diff --git a/.gitignore b/.gitignore index 2017073d7b72ec8afc14c30950ba209b829c4f1c..79796946618c59d07a83cdd1e7072f33a6f371bb 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,8 @@ node_modules/ *.env *.cache -app/scripts/latex-converter/input-example/ +app/scripts/latex-converter/input/ +app/scripts/latex-converter/output/ # PDF export app/public/*.pdf diff --git a/app/.astro/astro/content.d.ts b/app/.astro/astro/content.d.ts index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bba937b3dafa7018a200be372604d19a76078a60 100644 --- a/app/.astro/astro/content.d.ts +++ b/app/.astro/astro/content.d.ts @@ -0,0 +1,247 @@ +declare module 'astro:content' { + interface Render { + '.mdx': Promise<{ + Content: import('astro').MarkdownInstance<{}>['Content']; + headings: import('astro').MarkdownHeading[]; + remarkPluginFrontmatter: Record; + components: import('astro').MDXInstance<{}>['components']; + }>; + } +} + +declare module 'astro:content' { + interface RenderResult { + Content: import('astro/runtime/server/index.js').AstroComponentFactory; + headings: import('astro').MarkdownHeading[]; + remarkPluginFrontmatter: Record; + } + interface Render { + '.md': Promise; + } + + export interface RenderedContent { + html: string; + metadata?: { + imagePaths: Array; + [key: string]: unknown; + }; + } +} + +declare module 'astro:content' { + type Flatten = T extends { [K: string]: infer U } ? U : never; + + export type CollectionKey = keyof AnyEntryMap; + export type CollectionEntry = Flatten; + + export type ContentCollectionKey = keyof ContentEntryMap; + export type DataCollectionKey = keyof DataEntryMap; + + type AllValuesOf = T extends any ? T[keyof T] : never; + type ValidContentEntrySlug = AllValuesOf< + ContentEntryMap[C] + >['slug']; + + /** @deprecated Use `getEntry` instead. */ + export function getEntryBySlug< + C extends keyof ContentEntryMap, + E extends ValidContentEntrySlug | (string & {}), + >( + collection: C, + // Note that this has to accept a regular string too, for SSR + entrySlug: E, + ): E extends ValidContentEntrySlug + ? Promise> + : Promise | undefined>; + + /** @deprecated Use `getEntry` instead. */ + export function getDataEntryById( + collection: C, + entryId: E, + ): Promise>; + + export function getCollection>( + collection: C, + filter?: (entry: CollectionEntry) => entry is E, + ): Promise; + export function getCollection( + collection: C, + filter?: (entry: CollectionEntry) => unknown, + ): Promise[]>; + + export function getEntry< + C extends keyof ContentEntryMap, + E extends ValidContentEntrySlug | (string & {}), + >(entry: { + collection: C; + slug: E; + }): E extends ValidContentEntrySlug + ? Promise> + : Promise | undefined>; + export function getEntry< + C extends keyof DataEntryMap, + E extends keyof DataEntryMap[C] | (string & {}), + >(entry: { + collection: C; + id: E; + }): E extends keyof DataEntryMap[C] + ? Promise + : Promise | undefined>; + export function getEntry< + C extends keyof ContentEntryMap, + E extends ValidContentEntrySlug | (string & {}), + >( + collection: C, + slug: E, + ): E extends ValidContentEntrySlug + ? Promise> + : Promise | undefined>; + export function getEntry< + C extends keyof DataEntryMap, + E extends keyof DataEntryMap[C] | (string & {}), + >( + collection: C, + id: E, + ): E extends keyof DataEntryMap[C] + ? Promise + : Promise | undefined>; + + /** Resolve an array of entry references from the same collection */ + export function getEntries( + entries: { + collection: C; + slug: ValidContentEntrySlug; + }[], + ): Promise[]>; + export function getEntries( + entries: { + collection: C; + id: keyof DataEntryMap[C]; + }[], + ): Promise[]>; + + export function render( + entry: AnyEntryMap[C][string], + ): Promise; + + export function reference( + collection: C, + ): import('astro/zod').ZodEffects< + import('astro/zod').ZodString, + C extends keyof ContentEntryMap + ? { + collection: C; + slug: ValidContentEntrySlug; + } + : { + collection: C; + id: keyof DataEntryMap[C]; + } + >; + // Allow generic `string` to avoid excessive type errors in the config + // if `dev` is not running to update as you edit. + // Invalid collection names will be caught at build time. + export function reference( + collection: C, + ): import('astro/zod').ZodEffects; + + type ReturnTypeOrOriginal = T extends (...args: any[]) => infer R ? R : T; + type InferEntrySchema = import('astro/zod').infer< + ReturnTypeOrOriginal['schema']> + >; + + type ContentEntryMap = { + "chapters": { +"00_abstract.mdx": { + id: "00_abstract.mdx"; + slug: "00_abstract"; + body: string; + collection: "chapters"; + data: any +} & { render(): Render[".mdx"] }; +"01_introduction.mdx": { + id: "01_introduction.mdx"; + slug: "01_introduction"; + body: string; + collection: "chapters"; + data: any +} & { render(): Render[".mdx"] }; +"02_classic_robotics.mdx": { + id: "02_classic_robotics.mdx"; + slug: "02_classic_robotics"; + body: string; + collection: "chapters"; + data: any +} & { render(): Render[".mdx"] }; +"03_reinforcement_learning.mdx": { + id: "03_reinforcement_learning.mdx"; + slug: "03_reinforcement_learning"; + body: string; + collection: "chapters"; + data: any +} & { render(): Render[".mdx"] }; +"04_imitation_learning.mdx": { + id: "04_imitation_learning.mdx"; + slug: "04_imitation_learning"; + body: string; + collection: "chapters"; + data: any +} & { render(): Render[".mdx"] }; +"06_next_directions.mdx": { + id: "06_next_directions.mdx"; + slug: "06_next_directions"; + body: string; + collection: "chapters"; + data: any +} & { render(): Render[".mdx"] }; +"07_conclusions.mdx": { + id: "07_conclusions.mdx"; + slug: "07_conclusions"; + body: string; + collection: "chapters"; + data: any +} & { render(): Render[".mdx"] }; +"A_foreword.mdx": { + id: "A_foreword.mdx"; + slug: "a_foreword"; + body: string; + collection: "chapters"; + data: any +} & { render(): Render[".mdx"] }; +}; +"embeds": { +"vibe-code-d3-embeds-directives.md": { + id: "vibe-code-d3-embeds-directives.md"; + slug: "vibe-code-d3-embeds-directives"; + body: string; + collection: "embeds"; + data: any +} & { render(): Render[".md"] }; +}; +"test": { +"converted-sample.mdx": { + id: "converted-sample.mdx"; + slug: "converted-sample"; + body: string; + collection: "test"; + data: any +} & { render(): Render[".mdx"] }; +}; + + }; + + type DataEntryMap = { + "assets": { +"data/somedata": { + id: "data/somedata"; + collection: "assets"; + data: any +}; +}; + + }; + + type AnyEntryMap = ContentEntryMap & DataEntryMap; + + export type ContentConfig = never; +} diff --git a/app/scripts/latex-to-markdown/README.md b/app/scripts/latex-to-markdown/README.md deleted file mode 100644 index 2e068e91f0ae92820087720c29a73dc5e0b315f5..0000000000000000000000000000000000000000 --- a/app/scripts/latex-to-markdown/README.md +++ /dev/null @@ -1,158 +0,0 @@ -# LaTeX to Markdown Toolkit - -Conversion modulaire de projets LaTeX vers Markdown avec Pandoc. - -## 🚀 Usage rapide - -```bash -# Conversion complète (LaTeX + bibliographie) -node index.mjs --clean - -# Seulement nettoyer la bibliographie -node index.mjs --bib-only - -# Seulement convertir LaTeX (utilise une .bib existante) -node index.mjs --convert-only -``` - -## 📁 Structure - -``` -latex-to-markdown/ -├── index.mjs # Script principal -├── latex-converter.mjs # Convertisseur LaTeX → Markdown -├── bib-cleaner.mjs # Nettoyeur de bibliographie -├── input/ # Dossier source LaTeX -│ ├── main.tex -│ ├── main.bib -│ └── sections/ -└── output/ # Résultats de conversion - ├── main.md - └── main.bib -``` - -## 🔧 Modules - -### `index.mjs` - Script principal -Point d'entrée unifié avec options complètes. - -**Options :** -- `--input=PATH` : Fichier LaTeX source -- `--output=PATH` : Dossier de sortie -- `--clean` : Nettoyer le dossier de sortie -- `--bib-only` : Seulement nettoyer la bibliographie -- `--convert-only` : Seulement convertir (skip bibliographie) - -### `latex-converter.mjs` - Convertisseur -Conversion LaTeX vers Markdown avec Pandoc. - -**Fonctionnalités :** -- Support natif des macros LaTeX (`+latex_macros`) -- Gestion automatique des `\input{}` -- Citations avec `--citeproc` -- Extraction des images avec `--extract-media` -- Support mathématique avec `--mathjax` - -### `bib-cleaner.mjs` - Nettoyeur de bibliographie -Supprime les références externes des fichiers `.bib`. - -**Nettoyage :** -- Supprime les `file = {...}` (chemins locaux) -- Nettoie les virgules doubles -- Élimine les lignes vides - -## 📊 Exemple de workflow - -```bash -# 1. Placer vos fichiers LaTeX dans input/ -cp mon-projet/* input/ - -# 2. Conversion complète -node index.mjs --clean - -# 3. Récupérer les résultats dans output/ -ls output/ -# → main.md (Markdown converti) -# → main.bib (Bibliographie nettoyée) -``` - -## ⚙️ Configuration avancée - -### Chemins personnalisés -```bash -node index.mjs \ - --input=../paper/main.tex \ - --output=../results/ \ - --clean -``` - -### Usage programmatique -```javascript -import { convertLatexToMarkdown, cleanBibliography } from './index.mjs'; - -// Conversion LaTeX -await convertLatexToMarkdown('input.tex', 'output/'); - -// Nettoyage bibliographie -await cleanBibliography('refs.bib', 'clean-refs.bib'); -``` - -## 🛠️ Prérequis - -- **Node.js** avec support ESM -- **Pandoc** (`brew install pandoc`) - -## 🎯 Pandoc natif - -Le toolkit maximise l'utilisation de Pandoc natif : - -- **`+latex_macros`** : Gère `\newcommand`, `\renewcommand` -- **`--citeproc`** : Traitement automatique des citations -- **`--bibliography`** : Support de `.bib` files -- **`--extract-media`** : Images automatiques -- **`--mathjax`** : Mathématiques avancées - -Résultat : **95% Pandoc natif, 5% preprocessing minimal** ! 🚀 - ---- - -## 📝 État Actuel du Projet - -### ✅ Complété -- **Architecture modulaire** : `index.mjs` + `latex-converter.mjs` + `bib-cleaner.mjs` -- **Preprocessing LaTeX** : Gestion des `\input{}` pour assembler le document complet -- **Nettoyage bibliographie** : Suppression des références externes (`file = {...}`) -- **Conversion Pandoc** : Format `gfm+tex_math_dollars` pour compatibilité Astro -- **Citations sans crochets** : Format `@citationkey` au lieu de `[@citationkey]` -- **Gestion des commandes personnalisées** : `\actionchunk`, `\textsc`, `\gets`, etc. -- **Extraction d'images** : Support automatique via `--extract-media` - -### 🔧 En Cours de Résolution -- **Erreurs Pandoc sur certaines sections** : - - Section `03_reinforcement_learning.tex` contient des constructions LaTeX problématiques - - Erreur à la ligne 2011 : `unexpected }` dans le contexte de citations complexes - - Commande `\textsc{PopFront(\( \actionchunk_t \))}` mal interprétée - -### 🎯 Prochaines Étapes -1. **Déboguer section 03** : Identifier et corriger les constructions LaTeX incompatibles -2. **Test sections individuelles** : Valider chaque section séparément -3. **Améliorer preprocessing** : Ajouter plus de transformations pour gérer les cas complexes -4. **Validation finale** : Conversion complète du document - -### 🚨 Problèmes Identifiés -- **Commandes imbriquées** : `\textsc{...(\( math \))}` pose problème à Pandoc -- **Citations complexes** : Certains patterns de citations avec math inline -- **Math environments** : Quelques environnements non reconnus correctement - -### 🔍 Diagnostic -Dernière erreur : -``` -Error at "temp_main.tex" (line 2011, column 234): -unexpected } -expecting \end{document} -~@zhaoLearningFineGrainedBimanual2023 adopts a different strategy whereby... -\( \actionchunk_t \gets \pi(o_t) \) and chunk consumption -\( a_t \gets \textsc{PopFront(\( \actionchunk_t \))} \) -``` - -Le problème semble être dans l'imbrication de `\textsc{}` avec des expressions mathématiques à l'intérieur. diff --git a/app/scripts/latex-to-markdown/test-figure.tex b/app/scripts/latex-to-markdown/test-figure.tex deleted file mode 100644 index 13a118ef63bd5fd09f1e9a4ffbe28c981d85b266..0000000000000000000000000000000000000000 --- a/app/scripts/latex-to-markdown/test-figure.tex +++ /dev/null @@ -1,6 +0,0 @@ -\begin{figure} -\centering -\includegraphics{test.png} -\caption{Test figure} -\label{fig:test} -\end{figure} diff --git a/app/scripts/latex-to-mdx/README.md b/app/scripts/latex-to-mdx/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6eb428dba27e92ce07d8d24955a9f7d522d94e8d --- /dev/null +++ b/app/scripts/latex-to-mdx/README.md @@ -0,0 +1,169 @@ +# LaTeX to MDX Toolkit + +Complete LaTeX to MDX (Markdown + JSX) conversion optimized for Astro with advanced support for references, interactive equations, and components. + +## 🚀 Quick Start + +```bash +# Complete LaTeX → MDX conversion with all features +node index.mjs + +# For step-by-step debugging +node latex-converter.mjs # LaTeX → Markdown +node mdx-converter.mjs # Markdown → MDX +``` + +## 📁 Structure + +``` +latex-to-mdx/ +├── index.mjs # Complete LaTeX → MDX pipeline +├── latex-converter.mjs # LaTeX → Markdown with Pandoc +├── mdx-converter.mjs # Markdown → MDX with Astro components +├── reference-preprocessor.mjs # LaTeX references cleanup +├── post-processor.mjs # Markdown post-processing +├── bib-cleaner.mjs # Bibliography cleaner +├── filters/ +│ └── equation-ids.lua # Pandoc filter for KaTeX equations +├── input/ # LaTeX sources +│ ├── main.tex +│ ├── main.bib +│ └── sections/ +└── output/ # Results + ├── main.md # Intermediate Markdown + └── main.mdx # Final MDX for Astro +``` + +## ✨ Key Features + +### 🎯 **Smart References** +- **Invisible anchors**: Automatic conversion of `\label{}` to `` +- **Clean links**: Identifier cleanup (`:` → `-`, removing prefixes `sec:`, `fig:`, `eq:`) +- **Cross-references**: Full support for `\ref{}` with functional links + +### 🧮 **Interactive Equations** +- **KaTeX IDs**: Conversion of `\label{eq:...}` to `\htmlId{id}{equation}` +- **Equation references**: Clickable links to mathematical equations +- **Advanced KaTeX support**: `trust: true` configuration for `\htmlId{}` + +### 🎨 **Automatic Styling** +- **Highlights**: `\highlight{text}` → `text` +- **Auto cleanup**: Removal of numbering `(1)`, `(2)`, etc. +- **Astro components**: Images → `ResponsiveImage` with automatic imports + +### 🔧 **Robust Pipeline** +- **LaTeX preprocessor**: Reference cleanup before Pandoc +- **Lua filter**: Equation processing in Pandoc AST +- **Post-processor**: Markdown cleanup and optimization +- **MDX converter**: Final transformation with Astro components + +## 📊 Example Workflow + +```bash +# 1. Prepare LaTeX sources +cp my-paper/* input/ + +# 2. Complete automatic conversion +node index.mjs + +# 3. Generated results +ls output/ +# → main.md (Intermediate Markdown) +# → main.mdx (Final MDX for Astro) +# → assets/image/ (extracted images) +``` + +### 📋 Conversion Result + +The pipeline generates an MDX file optimized for Astro with: + +```mdx +--- +title: "Your Article Title" +description: "Generated from LaTeX" +--- + +import ResponsiveImage from '../components/ResponsiveImage.astro'; +import figure1 from '../assets/image/figure1.png'; + +## Section with invisible anchor + + +Here is some text with highlighted words. + +Reference to an interactive [equation](#equation-name). + +Equation with KaTeX ID: +$$\htmlId{equation-name}{E = mc^2}$$ + + +``` + +## ⚙️ Required Astro Configuration + +To use equations with IDs, add to `astro.config.mjs`: + +```javascript +import rehypeKatex from 'rehype-katex'; + +export default defineConfig({ + markdown: { + rehypePlugins: [ + [rehypeKatex, { trust: true }], // ← Important for \htmlId{} + ], + }, +}); +``` + +## 🛠️ Prerequisites + +- **Node.js** with ESM support +- **Pandoc** (`brew install pandoc`) +- **Astro** to use the generated MDX + +## 🎯 Technical Architecture + +### 4-Stage Pipeline + +1. **LaTeX Preprocessing** (`reference-preprocessor.mjs`) + - Cleanup of `\label{}` and `\ref{}` + - Conversion `\highlight{}` → CSS spans + - Removal of prefixes and problematic characters + +2. **Pandoc + Lua Filter** (`equation-ids.lua`) + - LaTeX → Markdown conversion with `gfm+tex_math_dollars+raw_html` + - Equation processing: `\label{eq:name}` → `\htmlId{name}{equation}` + - Automatic image extraction + +3. **Markdown Post-processing** (`post-processor.mjs`) + - KaTeX, Unicode, grouping commands cleanup + - Attribute correction with `:` + - Code snippet injection + +4. **MDX Conversion** (`mdx-converter.mjs`) + - Images transformation → `ResponsiveImage` + - HTML span escaping correction + - Automatic imports generation + - MDX frontmatter + +## 📊 Conversion Statistics + +For a typical scientific document: +- **87 labels** detected and processed +- **48 invisible anchors** created +- **13 highlight spans** with CSS class +- **4 equations** with `\htmlId{}` KaTeX +- **40 images** converted to components + +## ✅ Project Status + +### 🎉 **Complete Features** +- ✅ **LaTeX → MDX Pipeline**: Full end-to-end functional conversion +- ✅ **Cross-document references**: Perfectly functional internal links +- ✅ **Interactive equations**: KaTeX support with clickable IDs +- ✅ **Automatic styling**: Highlights and Astro components +- ✅ **Robustness**: Automatic cleanup of all escaping +- ✅ **Optimization**: Clean code without unnecessary elements + +### 🚀 **Production Ready** +The toolkit is now **100% operational** for converting complex scientific LaTeX documents to MDX/Astro with all advanced features (references, interactive equations, styling). diff --git a/app/scripts/latex-to-markdown/bib-cleaner.mjs b/app/scripts/latex-to-mdx/bib-cleaner.mjs similarity index 100% rename from app/scripts/latex-to-markdown/bib-cleaner.mjs rename to app/scripts/latex-to-mdx/bib-cleaner.mjs diff --git a/app/scripts/latex-to-markdown/filters/equation-ids.lua b/app/scripts/latex-to-mdx/filters/equation-ids.lua similarity index 74% rename from app/scripts/latex-to-markdown/filters/equation-ids.lua rename to app/scripts/latex-to-mdx/filters/equation-ids.lua index 4e97706ba9f915a2b1b6094c8bcfab44530222f4..86c980f35db55a4869708694930218c7fb32d219 100644 --- a/app/scripts/latex-to-markdown/filters/equation-ids.lua +++ b/app/scripts/latex-to-mdx/filters/equation-ids.lua @@ -48,17 +48,34 @@ function Math(el) -- Clean up any extra whitespace or line breaks that might remain clean_math = clean_math:gsub("%s*$", ""):gsub("^%s*", "") - -- Remove problematic equation environments that don't work well with \htmlId - clean_math = clean_math:gsub("\\begin%{equation%}", ""):gsub("\\end%{equation%}", "") - clean_math = clean_math:gsub("\\begin%{align%}", ""):gsub("\\end%{align%}", "") - clean_math = clean_math:gsub("\\begin%{equation%*%}", ""):gsub("\\end%{equation%*%}", "") - clean_math = clean_math:gsub("\\begin%{align%*%}", ""):gsub("\\end%{align%*%}", "") + -- Handle different equation environments appropriately + -- For align environments, preserve them as they work with KaTeX + local has_align = clean_math:match("\\begin%{align%}") + + if has_align then + -- For align environments, we keep the structure and add ID as an attribute + -- KaTeX supports align environments natively + clean_math = clean_math:gsub("\\begin%{align%}", "\\begin{align}") + clean_math = clean_math:gsub("\\end%{align%}", "\\end{align}") + else + -- Remove other equation environments that don't work well with \htmlId + clean_math = clean_math:gsub("\\begin%{equation%}", ""):gsub("\\end%{equation%}", "") + clean_math = clean_math:gsub("\\begin%{equation%*%}", ""):gsub("\\end%{equation%*%}", "") + clean_math = clean_math:gsub("\\begin%{align%*%}", ""):gsub("\\end%{align%*%}", "") + end -- Clean up any remaining whitespace clean_math = clean_math:gsub("%s*$", ""):gsub("^%s*", "") - -- Wrap the equation content with \htmlId{} - local new_math = "\\htmlId{" .. clean_id .. "}{" .. clean_math .. "}" + local new_math + if has_align then + -- For align environments, add the ID differently - KaTeX doesn't support \htmlId with align + -- Instead, we'll add a span with the ID right before the align + new_math = clean_math + else + -- For other math, wrap with \htmlId{} + new_math = "\\htmlId{" .. clean_id .. "}{" .. clean_math .. "}" + end -- Return new Math element with the updated content return pandoc.Math(el.mathtype, new_math) diff --git a/app/scripts/latex-to-markdown/index.mjs b/app/scripts/latex-to-mdx/index.mjs similarity index 100% rename from app/scripts/latex-to-markdown/index.mjs rename to app/scripts/latex-to-mdx/index.mjs diff --git a/app/scripts/latex-to-markdown/input/.gitignore b/app/scripts/latex-to-mdx/input/.gitignore similarity index 100% rename from app/scripts/latex-to-markdown/input/.gitignore rename to app/scripts/latex-to-mdx/input/.gitignore diff --git a/app/scripts/latex-to-markdown/input/README.md b/app/scripts/latex-to-mdx/input/README.md similarity index 100% rename from app/scripts/latex-to-markdown/input/README.md rename to app/scripts/latex-to-mdx/input/README.md diff --git a/app/scripts/latex-to-markdown/input/_minted/62B8750C0ACEBDA39A95140434E540A8.highlight.minted b/app/scripts/latex-to-mdx/input/_minted/62B8750C0ACEBDA39A95140434E540A8.highlight.minted similarity index 100% rename from app/scripts/latex-to-markdown/input/_minted/62B8750C0ACEBDA39A95140434E540A8.highlight.minted rename to app/scripts/latex-to-mdx/input/_minted/62B8750C0ACEBDA39A95140434E540A8.highlight.minted diff --git a/app/scripts/latex-to-markdown/input/_minted/_FAD58DE7366495DB4650CFEFAC2FCD61.index.minted b/app/scripts/latex-to-mdx/input/_minted/_FAD58DE7366495DB4650CFEFAC2FCD61.index.minted similarity index 100% rename from app/scripts/latex-to-markdown/input/_minted/_FAD58DE7366495DB4650CFEFAC2FCD61.index.minted rename to app/scripts/latex-to-mdx/input/_minted/_FAD58DE7366495DB4650CFEFAC2FCD61.index.minted diff --git a/app/scripts/latex-to-markdown/input/_minted/colorful.style.minted b/app/scripts/latex-to-mdx/input/_minted/colorful.style.minted similarity index 100% rename from app/scripts/latex-to-markdown/input/_minted/colorful.style.minted rename to app/scripts/latex-to-mdx/input/_minted/colorful.style.minted diff --git a/app/scripts/latex-to-markdown/input/fancyhdr.sty b/app/scripts/latex-to-mdx/input/fancyhdr.sty similarity index 100% rename from app/scripts/latex-to-markdown/input/fancyhdr.sty rename to app/scripts/latex-to-mdx/input/fancyhdr.sty diff --git a/app/scripts/latex-to-markdown/input/figures/ch1/ch1-lerobot-figure1.png b/app/scripts/latex-to-mdx/input/figures/ch1/ch1-lerobot-figure1.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch1/ch1-lerobot-figure1.png rename to app/scripts/latex-to-mdx/input/figures/ch1/ch1-lerobot-figure1.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch2/ch2-approaches.png b/app/scripts/latex-to-mdx/input/figures/ch2/ch2-approaches.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch2/ch2-approaches.png rename to app/scripts/latex-to-mdx/input/figures/ch2/ch2-approaches.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch2/ch2-classical-limitations.png b/app/scripts/latex-to-mdx/input/figures/ch2/ch2-classical-limitations.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch2/ch2-classical-limitations.png rename to app/scripts/latex-to-mdx/input/figures/ch2/ch2-classical-limitations.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch2/ch2-cost-accessibility.png b/app/scripts/latex-to-mdx/input/figures/ch2/ch2-cost-accessibility.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch2/ch2-cost-accessibility.png rename to app/scripts/latex-to-mdx/input/figures/ch2/ch2-cost-accessibility.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch2/ch2-planar-manipulator-floor-box.png b/app/scripts/latex-to-mdx/input/figures/ch2/ch2-planar-manipulator-floor-box.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch2/ch2-planar-manipulator-floor-box.png rename to app/scripts/latex-to-mdx/input/figures/ch2/ch2-planar-manipulator-floor-box.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch2/ch2-planar-manipulator-floor-shelf.png b/app/scripts/latex-to-mdx/input/figures/ch2/ch2-planar-manipulator-floor-shelf.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch2/ch2-planar-manipulator-floor-shelf.png rename to app/scripts/latex-to-mdx/input/figures/ch2/ch2-planar-manipulator-floor-shelf.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch2/ch2-planar-manipulator-floor.png b/app/scripts/latex-to-mdx/input/figures/ch2/ch2-planar-manipulator-floor.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch2/ch2-planar-manipulator-floor.png rename to app/scripts/latex-to-mdx/input/figures/ch2/ch2-planar-manipulator-floor.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch2/ch2-planar-manipulator-free.png b/app/scripts/latex-to-mdx/input/figures/ch2/ch2-planar-manipulator-free.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch2/ch2-planar-manipulator-free.png rename to app/scripts/latex-to-mdx/input/figures/ch2/ch2-planar-manipulator-free.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch2/ch2-platforms.png b/app/scripts/latex-to-mdx/input/figures/ch2/ch2-platforms.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch2/ch2-platforms.png rename to app/scripts/latex-to-mdx/input/figures/ch2/ch2-platforms.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch2/ch2-so100-to-planar-manipulator.png b/app/scripts/latex-to-mdx/input/figures/ch2/ch2-so100-to-planar-manipulator.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch2/ch2-so100-to-planar-manipulator.png rename to app/scripts/latex-to-mdx/input/figures/ch2/ch2-so100-to-planar-manipulator.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch3/ch3-agent-env.png b/app/scripts/latex-to-mdx/input/figures/ch3/ch3-agent-env.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch3/ch3-agent-env.png rename to app/scripts/latex-to-mdx/input/figures/ch3/ch3-agent-env.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch3/ch3-duck-sim-vs-real.png b/app/scripts/latex-to-mdx/input/figures/ch3/ch3-duck-sim-vs-real.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch3/ch3-duck-sim-vs-real.png rename to app/scripts/latex-to-mdx/input/figures/ch3/ch3-duck-sim-vs-real.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch3/ch3-hil-serl-examples.png b/app/scripts/latex-to-mdx/input/figures/ch3/ch3-hil-serl-examples.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch3/ch3-hil-serl-examples.png rename to app/scripts/latex-to-mdx/input/figures/ch3/ch3-hil-serl-examples.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch3/ch3-learning-atlas.png b/app/scripts/latex-to-mdx/input/figures/ch3/ch3-learning-atlas.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch3/ch3-learning-atlas.png rename to app/scripts/latex-to-mdx/input/figures/ch3/ch3-learning-atlas.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch3/ch3-learning-benefits.png b/app/scripts/latex-to-mdx/input/figures/ch3/ch3-learning-benefits.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch3/ch3-learning-benefits.png rename to app/scripts/latex-to-mdx/input/figures/ch3/ch3-learning-benefits.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch3/ch3-many-ducks.png b/app/scripts/latex-to-mdx/input/figures/ch3/ch3-many-ducks.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch3/ch3-many-ducks.png rename to app/scripts/latex-to-mdx/input/figures/ch3/ch3-many-ducks.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch3/ch3-rl-algorithms-atlas.png b/app/scripts/latex-to-mdx/input/figures/ch3/ch3-rl-algorithms-atlas.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch3/ch3-rl-algorithms-atlas.png rename to app/scripts/latex-to-mdx/input/figures/ch3/ch3-rl-algorithms-atlas.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch3/ch3-rl-examples.png b/app/scripts/latex-to-mdx/input/figures/ch3/ch3-rl-examples.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch3/ch3-rl-examples.png rename to app/scripts/latex-to-mdx/input/figures/ch3/ch3-rl-examples.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch4/ch4-act-decoder.png b/app/scripts/latex-to-mdx/input/figures/ch4/ch4-act-decoder.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch4/ch4-act-decoder.png rename to app/scripts/latex-to-mdx/input/figures/ch4/ch4-act-decoder.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch4/ch4-act-encoder.png b/app/scripts/latex-to-mdx/input/figures/ch4/ch4-act-encoder.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch4/ch4-act-encoder.png rename to app/scripts/latex-to-mdx/input/figures/ch4/ch4-act-encoder.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch4/ch4-act.png b/app/scripts/latex-to-mdx/input/figures/ch4/ch4-act.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch4/ch4-act.png rename to app/scripts/latex-to-mdx/input/figures/ch4/ch4-act.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch4/ch4-action-vs-observation-distribution.png b/app/scripts/latex-to-mdx/input/figures/ch4/ch4-action-vs-observation-distribution.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch4/ch4-action-vs-observation-distribution.png rename to app/scripts/latex-to-mdx/input/figures/ch4/ch4-action-vs-observation-distribution.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch4/ch4-async-inference.png b/app/scripts/latex-to-mdx/input/figures/ch4/ch4-async-inference.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch4/ch4-async-inference.png rename to app/scripts/latex-to-mdx/input/figures/ch4/ch4-async-inference.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch4/ch4-bc-trajectories.png b/app/scripts/latex-to-mdx/input/figures/ch4/ch4-bc-trajectories.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch4/ch4-bc-trajectories.png rename to app/scripts/latex-to-mdx/input/figures/ch4/ch4-bc-trajectories.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch4/ch4-diffusion-policy.png b/app/scripts/latex-to-mdx/input/figures/ch4/ch4-diffusion-policy.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch4/ch4-diffusion-policy.png rename to app/scripts/latex-to-mdx/input/figures/ch4/ch4-diffusion-policy.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch4/ch4-diffusion-robot-actions.png b/app/scripts/latex-to-mdx/input/figures/ch4/ch4-diffusion-robot-actions.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch4/ch4-diffusion-robot-actions.png rename to app/scripts/latex-to-mdx/input/figures/ch4/ch4-diffusion-robot-actions.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch4/ch4-diffusion-vs-flowmatching.png b/app/scripts/latex-to-mdx/input/figures/ch4/ch4-diffusion-vs-flowmatching.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch4/ch4-diffusion-vs-flowmatching.png rename to app/scripts/latex-to-mdx/input/figures/ch4/ch4-diffusion-vs-flowmatching.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch4/ch4-issues-with-bc.png b/app/scripts/latex-to-mdx/input/figures/ch4/ch4-issues-with-bc.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch4/ch4-issues-with-bc.png rename to app/scripts/latex-to-mdx/input/figures/ch4/ch4-issues-with-bc.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch4/ch4-latent-variable-model.png b/app/scripts/latex-to-mdx/input/figures/ch4/ch4-latent-variable-model.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch4/ch4-latent-variable-model.png rename to app/scripts/latex-to-mdx/input/figures/ch4/ch4-latent-variable-model.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch4/ch4-many-latents.png b/app/scripts/latex-to-mdx/input/figures/ch4/ch4-many-latents.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch4/ch4-many-latents.png rename to app/scripts/latex-to-mdx/input/figures/ch4/ch4-many-latents.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch4/ch4-normalizing-flows.png b/app/scripts/latex-to-mdx/input/figures/ch4/ch4-normalizing-flows.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch4/ch4-normalizing-flows.png rename to app/scripts/latex-to-mdx/input/figures/ch4/ch4-normalizing-flows.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch4/ch4-observation-action-mapping.png b/app/scripts/latex-to-mdx/input/figures/ch4/ch4-observation-action-mapping.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch4/ch4-observation-action-mapping.png rename to app/scripts/latex-to-mdx/input/figures/ch4/ch4-observation-action-mapping.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch4/ch4-queues.png b/app/scripts/latex-to-mdx/input/figures/ch4/ch4-queues.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch4/ch4-queues.png rename to app/scripts/latex-to-mdx/input/figures/ch4/ch4-queues.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch4/ch4-task-effect-on-pairs.png b/app/scripts/latex-to-mdx/input/figures/ch4/ch4-task-effect-on-pairs.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch4/ch4-task-effect-on-pairs.png rename to app/scripts/latex-to-mdx/input/figures/ch4/ch4-task-effect-on-pairs.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch5/ch5-generalist-policies-timeline.png b/app/scripts/latex-to-mdx/input/figures/ch5/ch5-generalist-policies-timeline.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch5/ch5-generalist-policies-timeline.png rename to app/scripts/latex-to-mdx/input/figures/ch5/ch5-generalist-policies-timeline.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch5/ch5-ml-vs-robotics-foundation.png b/app/scripts/latex-to-mdx/input/figures/ch5/ch5-ml-vs-robotics-foundation.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch5/ch5-ml-vs-robotics-foundation.png rename to app/scripts/latex-to-mdx/input/figures/ch5/ch5-ml-vs-robotics-foundation.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch5/ch5-pi0-sampling-timesteps.png b/app/scripts/latex-to-mdx/input/figures/ch5/ch5-pi0-sampling-timesteps.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch5/ch5-pi0-sampling-timesteps.png rename to app/scripts/latex-to-mdx/input/figures/ch5/ch5-pi0-sampling-timesteps.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch5/ch5-pi0.png b/app/scripts/latex-to-mdx/input/figures/ch5/ch5-pi0.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch5/ch5-pi0.png rename to app/scripts/latex-to-mdx/input/figures/ch5/ch5-pi0.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch5/ch5-smolvla.png b/app/scripts/latex-to-mdx/input/figures/ch5/ch5-smolvla.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch5/ch5-smolvla.png rename to app/scripts/latex-to-mdx/input/figures/ch5/ch5-smolvla.png diff --git a/app/scripts/latex-to-markdown/input/figures/ch5/ch5-trends.png b/app/scripts/latex-to-mdx/input/figures/ch5/ch5-trends.png similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/ch5/ch5-trends.png rename to app/scripts/latex-to-mdx/input/figures/ch5/ch5-trends.png diff --git a/app/scripts/latex-to-markdown/input/figures/misc/lerobot-team.jpeg b/app/scripts/latex-to-mdx/input/figures/misc/lerobot-team.jpeg similarity index 100% rename from app/scripts/latex-to-markdown/input/figures/misc/lerobot-team.jpeg rename to app/scripts/latex-to-mdx/input/figures/misc/lerobot-team.jpeg diff --git a/app/scripts/latex-to-markdown/input/handles.tex b/app/scripts/latex-to-mdx/input/handles.tex similarity index 100% rename from app/scripts/latex-to-markdown/input/handles.tex rename to app/scripts/latex-to-mdx/input/handles.tex diff --git a/app/scripts/latex-to-markdown/input/hfstyle/defns.tex b/app/scripts/latex-to-mdx/input/hfstyle/defns.tex similarity index 100% rename from app/scripts/latex-to-markdown/input/hfstyle/defns.tex rename to app/scripts/latex-to-mdx/input/hfstyle/defns.tex diff --git a/app/scripts/latex-to-markdown/input/hfstyle/hf.cls b/app/scripts/latex-to-mdx/input/hfstyle/hf.cls similarity index 100% rename from app/scripts/latex-to-markdown/input/hfstyle/hf.cls rename to app/scripts/latex-to-mdx/input/hfstyle/hf.cls diff --git a/app/scripts/latex-to-markdown/input/hfstyle/manrope.sty b/app/scripts/latex-to-mdx/input/hfstyle/manrope.sty similarity index 100% rename from app/scripts/latex-to-markdown/input/hfstyle/manrope.sty rename to app/scripts/latex-to-mdx/input/hfstyle/manrope.sty diff --git a/app/scripts/latex-to-markdown/input/hfstyle/manrope/Manrope-Bold.ttf b/app/scripts/latex-to-mdx/input/hfstyle/manrope/Manrope-Bold.ttf similarity index 100% rename from app/scripts/latex-to-markdown/input/hfstyle/manrope/Manrope-Bold.ttf rename to app/scripts/latex-to-mdx/input/hfstyle/manrope/Manrope-Bold.ttf diff --git a/app/scripts/latex-to-markdown/input/hfstyle/manrope/Manrope-Regular.ttf b/app/scripts/latex-to-mdx/input/hfstyle/manrope/Manrope-Regular.ttf similarity index 100% rename from app/scripts/latex-to-markdown/input/hfstyle/manrope/Manrope-Regular.ttf rename to app/scripts/latex-to-mdx/input/hfstyle/manrope/Manrope-Regular.ttf diff --git a/app/scripts/latex-to-markdown/input/hfstyle/plainnat.bst b/app/scripts/latex-to-mdx/input/hfstyle/plainnat.bst similarity index 100% rename from app/scripts/latex-to-markdown/input/hfstyle/plainnat.bst rename to app/scripts/latex-to-mdx/input/hfstyle/plainnat.bst diff --git a/app/scripts/latex-to-markdown/input/hfstyle/template_content.tex b/app/scripts/latex-to-mdx/input/hfstyle/template_content.tex similarity index 100% rename from app/scripts/latex-to-markdown/input/hfstyle/template_content.tex rename to app/scripts/latex-to-mdx/input/hfstyle/template_content.tex diff --git a/app/scripts/latex-to-markdown/input/main.bbl b/app/scripts/latex-to-mdx/input/main.bbl similarity index 100% rename from app/scripts/latex-to-markdown/input/main.bbl rename to app/scripts/latex-to-mdx/input/main.bbl diff --git a/app/scripts/latex-to-markdown/input/main.bib b/app/scripts/latex-to-mdx/input/main.bib similarity index 100% rename from app/scripts/latex-to-markdown/input/main.bib rename to app/scripts/latex-to-mdx/input/main.bib diff --git a/app/scripts/latex-to-markdown/input/main.dvi b/app/scripts/latex-to-mdx/input/main.dvi similarity index 100% rename from app/scripts/latex-to-markdown/input/main.dvi rename to app/scripts/latex-to-mdx/input/main.dvi diff --git a/app/scripts/latex-to-markdown/input/main.tex b/app/scripts/latex-to-mdx/input/main.tex similarity index 100% rename from app/scripts/latex-to-markdown/input/main.tex rename to app/scripts/latex-to-mdx/input/main.tex diff --git a/app/scripts/latex-to-markdown/input/manropebold.tfm b/app/scripts/latex-to-mdx/input/manropebold.tfm similarity index 100% rename from app/scripts/latex-to-markdown/input/manropebold.tfm rename to app/scripts/latex-to-mdx/input/manropebold.tfm diff --git a/app/scripts/latex-to-markdown/input/manroperegular.tfm b/app/scripts/latex-to-mdx/input/manroperegular.tfm similarity index 100% rename from app/scripts/latex-to-markdown/input/manroperegular.tfm rename to app/scripts/latex-to-mdx/input/manroperegular.tfm diff --git a/app/scripts/latex-to-markdown/input/math_commands.tex b/app/scripts/latex-to-mdx/input/math_commands.tex similarity index 100% rename from app/scripts/latex-to-markdown/input/math_commands.tex rename to app/scripts/latex-to-mdx/input/math_commands.tex diff --git a/app/scripts/latex-to-markdown/input/natbib.sty b/app/scripts/latex-to-mdx/input/natbib.sty similarity index 100% rename from app/scripts/latex-to-markdown/input/natbib.sty rename to app/scripts/latex-to-mdx/input/natbib.sty diff --git a/app/scripts/latex-to-markdown/input/preamble.tex b/app/scripts/latex-to-mdx/input/preamble.tex similarity index 100% rename from app/scripts/latex-to-markdown/input/preamble.tex rename to app/scripts/latex-to-mdx/input/preamble.tex diff --git a/app/scripts/latex-to-markdown/input/sections/00_abstract.tex b/app/scripts/latex-to-mdx/input/sections/00_abstract.tex similarity index 100% rename from app/scripts/latex-to-markdown/input/sections/00_abstract.tex rename to app/scripts/latex-to-mdx/input/sections/00_abstract.tex diff --git a/app/scripts/latex-to-markdown/input/sections/01_introduction.tex b/app/scripts/latex-to-mdx/input/sections/01_introduction.tex similarity index 100% rename from app/scripts/latex-to-markdown/input/sections/01_introduction.tex rename to app/scripts/latex-to-mdx/input/sections/01_introduction.tex diff --git a/app/scripts/latex-to-markdown/input/sections/02_classic_robotics.tex b/app/scripts/latex-to-mdx/input/sections/02_classic_robotics.tex similarity index 100% rename from app/scripts/latex-to-markdown/input/sections/02_classic_robotics.tex rename to app/scripts/latex-to-mdx/input/sections/02_classic_robotics.tex diff --git a/app/scripts/latex-to-markdown/input/sections/03_reinforcement_learning.tex b/app/scripts/latex-to-mdx/input/sections/03_reinforcement_learning.tex similarity index 100% rename from app/scripts/latex-to-markdown/input/sections/03_reinforcement_learning.tex rename to app/scripts/latex-to-mdx/input/sections/03_reinforcement_learning.tex diff --git a/app/scripts/latex-to-markdown/input/sections/04_imitation_learning.tex b/app/scripts/latex-to-mdx/input/sections/04_imitation_learning.tex similarity index 100% rename from app/scripts/latex-to-markdown/input/sections/04_imitation_learning.tex rename to app/scripts/latex-to-mdx/input/sections/04_imitation_learning.tex diff --git a/app/scripts/latex-to-markdown/input/sections/05_foundation_models.tex b/app/scripts/latex-to-mdx/input/sections/05_foundation_models.tex similarity index 100% rename from app/scripts/latex-to-markdown/input/sections/05_foundation_models.tex rename to app/scripts/latex-to-mdx/input/sections/05_foundation_models.tex diff --git a/app/scripts/latex-to-markdown/input/sections/05_foundation_models.tex.temp b/app/scripts/latex-to-mdx/input/sections/05_foundation_models.tex.temp similarity index 100% rename from app/scripts/latex-to-markdown/input/sections/05_foundation_models.tex.temp rename to app/scripts/latex-to-mdx/input/sections/05_foundation_models.tex.temp diff --git a/app/scripts/latex-to-markdown/input/sections/06_next_directions.tex b/app/scripts/latex-to-mdx/input/sections/06_next_directions.tex similarity index 100% rename from app/scripts/latex-to-markdown/input/sections/06_next_directions.tex rename to app/scripts/latex-to-mdx/input/sections/06_next_directions.tex diff --git a/app/scripts/latex-to-markdown/input/sections/07_conclusions.tex b/app/scripts/latex-to-mdx/input/sections/07_conclusions.tex similarity index 100% rename from app/scripts/latex-to-markdown/input/sections/07_conclusions.tex rename to app/scripts/latex-to-mdx/input/sections/07_conclusions.tex diff --git a/app/scripts/latex-to-markdown/input/sections/A_foreword.tex b/app/scripts/latex-to-mdx/input/sections/A_foreword.tex similarity index 100% rename from app/scripts/latex-to-markdown/input/sections/A_foreword.tex rename to app/scripts/latex-to-mdx/input/sections/A_foreword.tex diff --git a/app/scripts/latex-to-markdown/input/sections/test.md b/app/scripts/latex-to-mdx/input/sections/test.md similarity index 100% rename from app/scripts/latex-to-markdown/input/sections/test.md rename to app/scripts/latex-to-mdx/input/sections/test.md diff --git a/app/scripts/latex-to-markdown/input/snippets/01_1_datasets.py b/app/scripts/latex-to-mdx/input/snippets/01_1_datasets.py similarity index 100% rename from app/scripts/latex-to-markdown/input/snippets/01_1_datasets.py rename to app/scripts/latex-to-mdx/input/snippets/01_1_datasets.py diff --git a/app/scripts/latex-to-markdown/input/t1manrope.fd b/app/scripts/latex-to-mdx/input/t1manrope.fd similarity index 100% rename from app/scripts/latex-to-markdown/input/t1manrope.fd rename to app/scripts/latex-to-mdx/input/t1manrope.fd diff --git a/app/scripts/latex-to-markdown/latex-converter.mjs b/app/scripts/latex-to-mdx/latex-converter.mjs similarity index 89% rename from app/scripts/latex-to-markdown/latex-converter.mjs rename to app/scripts/latex-to-mdx/latex-converter.mjs index 9f57143b5757f7cd3d3431509a64521a88800784..7079e2e43b85e9947a771a33a9ef22adb329f35a 100644 --- a/app/scripts/latex-to-markdown/latex-converter.mjs +++ b/app/scripts/latex-to-mdx/latex-converter.mjs @@ -67,13 +67,23 @@ function preprocessLatexFile(inputFile, outputDir) { content = content.replace(/\\end\{equation\*\}\$\$/g, '$$'); content = content.replace(/\\begin\{equation\*\}/g, '$$'); content = content.replace(/\\end\{equation\*\}/g, '$$'); - content = content.replace(/\\begin\{align\}/g, '$$'); - content = content.replace(/\\end\{align\}/g, '$$'); + // Keep align environments intact for KaTeX support + // Protect align environments by temporarily replacing them before cleaning & operators + const alignBlocks = []; + content = content.replace(/\\begin\{align\}([\s\S]*?)\\end\{align\}/g, (match, alignContent) => { + alignBlocks.push(match); + return `__ALIGN_BLOCK_${alignBlocks.length - 1}__`; + }); - // Remove ALL alignment operators (&) from math content - purely cosmetic + // Now remove & operators from non-align content (outside align environments) content = content.replace(/&=/g, '='); content = content.replace(/&/g, ''); + // Restore align blocks with their & operators intact + alignBlocks.forEach((block, index) => { + content = content.replace(`__ALIGN_BLOCK_${index}__`, block); + }); + // Convert LaTeX citations to Pandoc format content = content.replace(/\\cite[tp]?\{([^}]+)\}/g, (match, citations) => { // Handle multiple citations separated by commas - all become simple @citations @@ -144,17 +154,27 @@ function preprocessLatexFile(inputFile, outputDir) { return `\\text{${simplified}}`; }); - // Remove alignment operators in included content too + // Apply same align-preserving logic to included content + const alignBlocksIncluded = []; + includedContent = includedContent.replace(/\\begin\{align\}([\s\S]*?)\\end\{align\}/g, (match, alignContent) => { + alignBlocksIncluded.push(match); + return `__ALIGN_BLOCK_${alignBlocksIncluded.length - 1}__`; + }); + + // Remove alignment operators from non-align content in included files includedContent = includedContent.replace(/&=/g, '='); includedContent = includedContent.replace(/&/g, ''); + // Restore align blocks with their & operators intact + alignBlocksIncluded.forEach((block, index) => { + includedContent = includedContent.replace(`__ALIGN_BLOCK_${index}__`, block); + }); + // Convert math environments in included content includedContent = includedContent.replace(/\$\$\\begin\{equation\*\}/g, '$$'); includedContent = includedContent.replace(/\\end\{equation\*\}\$\$/g, '$$'); includedContent = includedContent.replace(/\\begin\{equation\*\}/g, '$$'); includedContent = includedContent.replace(/\\end\{equation\*\}/g, '$$'); - includedContent = includedContent.replace(/\\begin\{align\}/g, '$$'); - includedContent = includedContent.replace(/\\end\{align\}/g, '$$'); // Convert citations in included content includedContent = includedContent.replace(/\\cite[tp]?\{([^}]+)\}/g, (match, citations) => { diff --git a/app/scripts/latex-to-markdown/mdx-converter.mjs b/app/scripts/latex-to-mdx/mdx-converter.mjs similarity index 80% rename from app/scripts/latex-to-markdown/mdx-converter.mjs rename to app/scripts/latex-to-mdx/mdx-converter.mjs index 35416a144a048bce821201600831308fdece576c..292027eabaa75fc18f34add9f2a688753a67fe6c 100644 --- a/app/scripts/latex-to-markdown/mdx-converter.mjs +++ b/app/scripts/latex-to-mdx/mdx-converter.mjs @@ -328,7 +328,6 @@ function fixHtmlEscaping(content) { let fixedCount = 0; - // Fix escaped HTML in anchor spans with various escaping patterns // Pattern 1: \\ content = content.replace(/\\\\<\/span\\>/g, (match, id, style) => { fixedCount++; @@ -348,6 +347,26 @@ function fixHtmlEscaping(content) { return `${cleanText}`; }); + // Pattern 3: HTML-encoded spans in paragraph tags + //

<span id="..." style="..."></span>

+ content = content.replace(/

<span id="([^"]*)" style="([^"]*)"><\/span><\/p>/g, (match, id, style) => { + fixedCount++; + // Fix common style issues like "position- absolute;" -> "position: absolute;" + const cleanStyle = style.replace('position- absolute;', 'position: absolute;'); + return ``; + }); + + // Pattern 4: HTML-encoded spans with class in paragraph tags + //

<span class="...">...</span>

+ content = content.replace(/

<span class="([^"]*)">([^&]*)<\/span><\/p>/g, (match, className, text) => { + fixedCount++; + // Remove numbering like (1), (2), (3) from highlight spans + let cleanText = text; + if (className === 'highlight') { + cleanText = text.replace(/^\(\d+\)\s*/, ''); + } + return `${cleanText}`; + }); if (fixedCount > 0) { console.log(` ✅ Fixed ${fixedCount} escaped span(s)`); @@ -425,6 +444,115 @@ tableOfContentsAutoCollapse: true return content; } +/** + * Fix mixed math delimiters like $`...`$ or `...`$ + * @param {string} content - MDX content + * @returns {string} - Content with fixed math delimiters + */ +function fixMixedMathDelimiters(content) { + console.log(' 🔧 Fixing mixed math delimiters...'); + + let fixedCount = 0; + + // Fix patterns like $`...`$ (mixed delimiters) + content = content.replace(/\$`([^`]*)`\$/g, (match, mathContent) => { + fixedCount++; + return `$${mathContent}$`; + }); + + // Fix patterns like `...`$ (backtick start, dollar end) + content = content.replace(/`([^`]*)`\$/g, (match, mathContent) => { + fixedCount++; + return `$${mathContent}$`; + }); + + // Fix patterns like $`...` (dollar start, backtick end - less common) + content = content.replace(/\$`([^`]*)`(?!\$)/g, (match, mathContent) => { + fixedCount++; + return `$${mathContent}$`; + }); + + if (fixedCount > 0) { + console.log(` ✅ Fixed ${fixedCount} mixed math delimiter(s)`); + } + + return content; +} + +/** + * Clean up orphaned math delimiters and fix mixed content + * @param {string} content - MDX content + * @returns {string} - Content with cleaned math blocks + */ +function cleanOrphanedMathDelimiters(content) { + console.log(' 🧹 Cleaning orphaned math delimiters...'); + console.log(' 🔍 Content length:', content.length, 'chars'); + + let fixedCount = 0; + + // Fix orphaned $$ that are alone on lines (but not part of display math blocks) + // Only remove $$ that appear alone without corresponding closing $$ + content = content.replace(/^\$\$\s*$(?!\s*[\s\S]*?\$\$)/gm, () => { + fixedCount++; + return ''; + }); + + // Fix backticks inside $$....$$ blocks (Pandoc artifact) + const mathMatches = content.match(/\$\$([\s\S]*?)\$\$/g); + console.log(` 🔍 Found ${mathMatches ? mathMatches.length : 0} math blocks`); + + content = content.replace(/\$\$([\s\S]*?)\$\$/g, (match, mathContent) => { + // More aggressive: remove ALL single backticks in math blocks (they shouldn't be there) + let cleanedMath = mathContent; + + // Count backticks before + const backticksBefore = (mathContent.match(/`/g) || []).length; + + if (backticksBefore > 0) { + console.log(` 🔧 Found math block with ${backticksBefore} backtick(s)`); + } + + // Remove all isolated backticks (not in pairs) + cleanedMath = cleanedMath.replace(/`/g, ''); + + const backticksAfter = (cleanedMath.match(/`/g) || []).length; + + if (backticksBefore > 0) { + fixedCount++; + console.log(` 🔧 Removed ${backticksBefore} backtick(s) from math block`); + return `$$${cleanedMath}$$`; + } + return match; + }); + + // Fix escaped align in math blocks: \begin{align} -> \begin{align} + content = content.replace(/\\begin\{align\}/g, (match) => { + fixedCount++; + return '\\begin{align}'; + }); + + content = content.replace(/\\end\{align\}/g, (match) => { + fixedCount++; + return '\\end{align}'; + }); + + // Fix cases where text gets mixed with math blocks + // Pattern: ``` math ... ``` text ``` math + content = content.replace(/``` math\s*\n([\s\S]*?)\n```\s*([^`\n]*?)\s*``` math/g, (match, math1, text, math2) => { + if (text.trim().length > 0 && !text.includes('```')) { + fixedCount++; + return '```' + ' math\n' + math1 + '\n```\n\n' + text.trim() + '\n\n```' + ' math'; + } + return match; + }); + + if (fixedCount > 0) { + console.log(` ✅ Fixed ${fixedCount} orphaned math delimiter(s)`); + } + + return content; +} + /** * Clean newlines from single-dollar math blocks ($...$) ONLY * @param {string} content - MDX content @@ -559,12 +687,11 @@ function cleanMdxSyntax(content) { console.log(' 🧹 Cleaning MDX syntax...'); return content - // Fix math delimiters - .replace(/\$`([^`]+)`\$/g, '$$$1$$') + // NOTE: Math delimiter fixing is now handled by fixMixedMathDelimiters() // Ensure proper spacing around JSX-like constructs .replace(/>\s*\n<') - // Remove problematic heading attributes - .replace(/^(#{1,6}[^{]+)\{[^}]+\}$/gm, '$1') + // Remove problematic heading attributes - be more specific to avoid matching \begin{align} + .replace(/^(#{1,6}\s+[^{#\n]+)\{[^}]+\}$/gm, '$1') // Fix escaped quotes in text .replace(/\\("|')/g, '$1'); } @@ -586,6 +713,13 @@ function processMdxContent(content, latexContent = '') { // Apply each transformation step sequentially processedContent = ensureFrontmatter(processedContent, latexContent); + processedContent = fixMixedMathDelimiters(processedContent); + + // Debug: check for $$ blocks after fixMixedMathDelimiters + const mathBlocksAfterMixed = (processedContent.match(/\$\$([\s\S]*?)\$\$/g) || []).length; + console.log(` 📊 Math blocks after mixed delimiters fix: ${mathBlocksAfterMixed}`); + + processedContent = cleanOrphanedMathDelimiters(processedContent); processedContent = cleanSingleLineMathNewlines(processedContent); processedContent = formatDisplayMathBlocks(processedContent); processedContent = removeHtmlComments(processedContent); diff --git a/app/scripts/latex-to-markdown/metadata-extractor.mjs b/app/scripts/latex-to-mdx/metadata-extractor.mjs similarity index 100% rename from app/scripts/latex-to-markdown/metadata-extractor.mjs rename to app/scripts/latex-to-mdx/metadata-extractor.mjs diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch1/ch1-lerobot-figure1.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch1/ch1-lerobot-figure1.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch1/ch1-lerobot-figure1.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch1/ch1-lerobot-figure1.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch2/ch2-approaches.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch2/ch2-approaches.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch2/ch2-approaches.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch2/ch2-approaches.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch2/ch2-classical-limitations.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch2/ch2-classical-limitations.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch2/ch2-classical-limitations.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch2/ch2-classical-limitations.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch2/ch2-cost-accessibility.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch2/ch2-cost-accessibility.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch2/ch2-cost-accessibility.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch2/ch2-cost-accessibility.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch2/ch2-planar-manipulator-floor-box.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch2/ch2-planar-manipulator-floor-box.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch2/ch2-planar-manipulator-floor-box.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch2/ch2-planar-manipulator-floor-box.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch2/ch2-planar-manipulator-floor-shelf.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch2/ch2-planar-manipulator-floor-shelf.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch2/ch2-planar-manipulator-floor-shelf.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch2/ch2-planar-manipulator-floor-shelf.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch2/ch2-planar-manipulator-floor.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch2/ch2-planar-manipulator-floor.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch2/ch2-planar-manipulator-floor.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch2/ch2-planar-manipulator-floor.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch2/ch2-planar-manipulator-free.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch2/ch2-planar-manipulator-free.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch2/ch2-planar-manipulator-free.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch2/ch2-planar-manipulator-free.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch2/ch2-platforms.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch2/ch2-platforms.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch2/ch2-platforms.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch2/ch2-platforms.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch2/ch2-so100-to-planar-manipulator.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch2/ch2-so100-to-planar-manipulator.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch2/ch2-so100-to-planar-manipulator.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch2/ch2-so100-to-planar-manipulator.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch3/ch3-agent-env.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch3/ch3-agent-env.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch3/ch3-agent-env.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch3/ch3-agent-env.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch3/ch3-duck-sim-vs-real.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch3/ch3-duck-sim-vs-real.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch3/ch3-duck-sim-vs-real.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch3/ch3-duck-sim-vs-real.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch3/ch3-hil-serl-examples.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch3/ch3-hil-serl-examples.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch3/ch3-hil-serl-examples.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch3/ch3-hil-serl-examples.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch3/ch3-learning-atlas.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch3/ch3-learning-atlas.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch3/ch3-learning-atlas.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch3/ch3-learning-atlas.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch3/ch3-learning-benefits.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch3/ch3-learning-benefits.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch3/ch3-learning-benefits.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch3/ch3-learning-benefits.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch3/ch3-many-ducks.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch3/ch3-many-ducks.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch3/ch3-many-ducks.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch3/ch3-many-ducks.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch3/ch3-rl-algorithms-atlas.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch3/ch3-rl-algorithms-atlas.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch3/ch3-rl-algorithms-atlas.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch3/ch3-rl-algorithms-atlas.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch3/ch3-rl-examples.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch3/ch3-rl-examples.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch3/ch3-rl-examples.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch3/ch3-rl-examples.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-act-decoder.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-act-decoder.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-act-decoder.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-act-decoder.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-act-encoder.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-act-encoder.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-act-encoder.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-act-encoder.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-act.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-act.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-act.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-act.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-action-vs-observation-distribution.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-action-vs-observation-distribution.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-action-vs-observation-distribution.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-action-vs-observation-distribution.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-async-inference.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-async-inference.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-async-inference.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-async-inference.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-bc-trajectories.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-bc-trajectories.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-bc-trajectories.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-bc-trajectories.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-diffusion-policy.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-diffusion-policy.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-diffusion-policy.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-diffusion-policy.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-diffusion-robot-actions.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-diffusion-robot-actions.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-diffusion-robot-actions.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-diffusion-robot-actions.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-diffusion-vs-flowmatching.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-diffusion-vs-flowmatching.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-diffusion-vs-flowmatching.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-diffusion-vs-flowmatching.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-issues-with-bc.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-issues-with-bc.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-issues-with-bc.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-issues-with-bc.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-latent-variable-model.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-latent-variable-model.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-latent-variable-model.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-latent-variable-model.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-many-latents.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-many-latents.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-many-latents.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-many-latents.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-normalizing-flows.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-normalizing-flows.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-normalizing-flows.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-normalizing-flows.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-observation-action-mapping.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-observation-action-mapping.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-observation-action-mapping.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-observation-action-mapping.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-queues.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-queues.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-queues.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-queues.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-task-effect-on-pairs.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-task-effect-on-pairs.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch4/ch4-task-effect-on-pairs.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch4/ch4-task-effect-on-pairs.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch5/ch5-generalist-policies-timeline.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch5/ch5-generalist-policies-timeline.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch5/ch5-generalist-policies-timeline.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch5/ch5-generalist-policies-timeline.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch5/ch5-ml-vs-robotics-foundation.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch5/ch5-ml-vs-robotics-foundation.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch5/ch5-ml-vs-robotics-foundation.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch5/ch5-ml-vs-robotics-foundation.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch5/ch5-pi0-sampling-timesteps.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch5/ch5-pi0-sampling-timesteps.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch5/ch5-pi0-sampling-timesteps.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch5/ch5-pi0-sampling-timesteps.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch5/ch5-pi0.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch5/ch5-pi0.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch5/ch5-pi0.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch5/ch5-pi0.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch5/ch5-smolvla.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch5/ch5-smolvla.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch5/ch5-smolvla.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch5/ch5-smolvla.png diff --git a/app/scripts/latex-to-markdown/output/assets/image/figures/ch5/ch5-trends.png b/app/scripts/latex-to-mdx/output/assets/image/figures/ch5/ch5-trends.png similarity index 100% rename from app/scripts/latex-to-markdown/output/assets/image/figures/ch5/ch5-trends.png rename to app/scripts/latex-to-mdx/output/assets/image/figures/ch5/ch5-trends.png diff --git a/app/scripts/latex-to-markdown/output/main.bib b/app/scripts/latex-to-mdx/output/main.bib similarity index 100% rename from app/scripts/latex-to-markdown/output/main.bib rename to app/scripts/latex-to-mdx/output/main.bib diff --git a/app/scripts/latex-to-markdown/output/main.md b/app/scripts/latex-to-mdx/output/main.md similarity index 91% rename from app/scripts/latex-to-markdown/output/main.md rename to app/scripts/latex-to-mdx/output/main.md index e004d496d0d2b622f0988ae2876c4e92412cb141..09244c795e617253e31bd48b79b77bd43a42c0de 100644 --- a/app/scripts/latex-to-markdown/output/main.md +++ b/app/scripts/latex-to-mdx/output/main.md @@ -19,7 +19,7 @@ We sincerely hope this tutorial serves as a valuable starting point for your jou ## Introduction

- +

<span id="figure1" style="position: absolute;"></span>

lerobot is the open-source library for end-to-end robotics developed by Hugging Face. The library is vertically integrated on the entire robotics stack, supporting low-level control of real-world robot devices, advanced data and inference optimizations, as well as SOTA robot learning methods with simple implementations in pure Pytorch.
@@ -164,7 +164,7 @@ TL;DR Learning-based approaches to robotics are motivated by the need to (1) gen ### Explicit and Implicit Models
- +

<span id="generating-motion-atlas" style="position: absolute;"></span>

Overview of methods to generate motion (clearly non-exhausitve, see @bekrisStateRobotMotion2024). The different methods can be grouped based on whether they explicitly (dynamics-based) or implicitly (learning-based) model robot-environment interactions.
@@ -176,7 +176,7 @@ Methods to produce robotics motion range from traditional *explicit* models--\ - +

<span id="robotics-platforms-atlas" style="position: absolute;"></span>

Different kinds of motions are achieved with potentially very different robotic platforms. From left to right, top to bottom: ViperX, SO-100, Boston Dynamics’ Spot, Open-Duck, 1X’s NEO, Boston Dynamics’ Atlas. This is an example list of robotic platforms and is (very) far from being exhaustive.
@@ -194,7 +194,7 @@ Robot manipulators typically consist of a series of links and joints, articulate Recently, the development of low-cost manipulators like the ALOHA @zhaoLearningFineGrainedBimanual2023 ALOHA-2 @aldacoALOHA2Enhanced and SO-100/SO-101 @knightStandardOpenSO100 platforms significantly lowered the barrier to entry to robotics, considering the increased accessibility of these robots compared to more traditional platforms like the Franka Emika Panda arm (Figure [robotic-platforms-costs]).
- +

<span id="robotic-platforms-costs" style="position: absolute;"></span>

Cheaper, more accessible robots are starting to rival traditional platforms like the Panda arm platforms in adoption in resource-constrained scenarios. The SO-100, in particular, has a cost in the 100s of Euros, and can be entirely 3D-printed in hours, while the industrially-manufactured Panda arm costs tens of thousands of Euros and is not openly available.
@@ -202,7 +202,7 @@ Recently, the development of low-cost manipulators like the ALOHA @zhaoLearning Deriving an intuition as per why learning-based approaches are gaining popularity in the robotics community requires briefly analyzing traditional approaches for manipulation, leveraging tools like forward and inverse kinematics (FK, IK) and control theory. Providing a detailed overview of these methods falls (well) out of the scope of this tutorial, and we refer the reader to works including @sicilianoSpringerHandbookRobotics2016, @lynchModernRoboticsMechanics2017, @tedrakeRoboticManipulationPerception, @tedrakeUnderactuatedRoboticsAlgorithms for a much more comprehensive description of these techniques. Here, we mostly wish to highlight the benefits of ML over these traditional techniques
- +

<span id="make-so100-planar-manipulator" style="position: absolute;"></span>

The SO-100 arm is a 6-dof manipulator arm. Preventing some of its joints (shoulder pane, wrist flex and wrist roll) from actuating, it can be represented as a traditional 2-dof planar manipulator (the gripper joint in the end-effector is not considered towards the count of the degrees of freedom used to produce motion).
@@ -215,44 +215,45 @@ All these simplifying assumptions leave us with the planar manipulator of Figure
- +

<span id="planar-manipulation-simple" style="position: absolute;"></span>

Free to move
- +

<span id="planar-manipulator-floor" style="position: absolute;"></span>

Constrained by the surface
- +

<span id="planar-manipulator-floor-shelf" style="position: absolute;"></span>

Constrained by surface and (fixed) obstacle
Planar, 2-dof schematic representation of the SO-100 manipulator under diverse deployment settings. From left to right: completely free of moving; constrained by the presence of the surface; constrained by the surface and presence of obstacles. Circular arrows around each joint indicate the maximal rotation feasible at that joint.
-Considering the (toy) example presented in Figure [planar-manipulation-simple], then we can analytically write the end-effector’s position $`p \in \mathbb R^2`$ as a function of the robot’s configuration, $`p = p(q), p: \mathcal Q \mapsto \mathbb R^2`$. In particular, we have: -$$ -`p(q) = +Considering the (toy) example presented in Figure [planar-manipulation-simple], then we can analytically write the end-effector’s position $`p \in \mathbb R^2`$ as a function of the robot’s configuration, $`p = p(q), p: \mathcal Q \mapsto \mathbb R^2`$. In particular, we have: $`p(q) = \begin{pmatrix} -p_x(\theta_1, \theta_2)\\ - p_y(\theta_1, \theta_2) +p_x(\theta_1, \theta_2) \\ +p_y(\theta_1, \theta_2) \end{pmatrix} = \begin{pmatrix} -l \cos(\theta_1) + l \cos(\theta_1 + \theta_2)\\ - l \sin(\theta_1) + l \sin(\theta_1 + \theta_2) +l \cos(\theta_1) + l \cos(\theta_1 + \theta_2) \\ +l \sin(\theta_1) + l \sin(\theta_1 + \theta_2) \end{pmatrix} -\in S^{n=2}_{l_1+l_2} = \{ p(q) \in \mathbb R^2: \Vert p(q) \Vert_2^2 \leq (2l)^2, \ \forall q \in \mathcal Q \}` -$$ - +\in S^{n=2}_{l_1+l_2} = \{ p(q) \in \mathbb R^2: \Vert p(q) \Vert_2^2 \leq (2l)^2, \ \forall q \in \mathcal Q \}`$ Deriving the end-effector’s *pose*--position *and* orientation--in some $`m`$-dimensional space $`\boldsymbol{p} \in \mathcal{P} \subset \mathbb{R}^{m}`$ starting from the configuration $`{\textnormal{q}}\in \mathcal Q \subset \mathbb R^n`$ of a $`n`$-joints robot is referred to as *forward kinematics* (FK), whereas identifying the configuration corresponding to any given target pose is termed *inverse kinematics* (IK). In that, FK is used to map a robot configuration into the corresponding end-effector pose, whereas IK is used to reconstruct the configuration(s) given an end-effector pose. In the simplified case here considered (for which $`\boldsymbol{p} \equiv p`$, as the orientation of the end-effector is disregarded for simplicity), one can solve the problem of controlling the end-effector’s location to reach a goal position $`p^*`$ by solving analytically for $`q: p(q) = f_{\text{FK}}(q) = p^*`$. However, in the general case, one might not be able to solve this problem analytically, and can typically resort to iterative optimization methods comparing candidate solutions using a loss function (in the simplest case, $`\Vert p(q) - p^* \Vert_2^2`$ is a natural candidate), yielding: -$`\htmlId{ik_problem}{\min_{q \in \mathcal Q} \Vert p(q) - p^* \Vert_2^2 \, .}`$ +``` math +\begin{align} +\min_{q \in \mathcal Q} \Vert p(q) - p^* \Vert_2^2 \, . + +\end{align} +``` Exact analytical solutions to IK are even less appealing when one considers the presence of obstacles in the robot’s workspace, resulting in constraints on the possible values of $`q \in \mathcal Q \subseteq [-\pi, +\pi]^n \subset \mathbb R^n`$ in the general case of $`n`$-links robots. @@ -260,7 +261,13 @@ For instance, the robot in Figure [ik_problem] for a feasible $`q`$--only proves useful in determining information regarding the robot’s configuration in the goal pose, and crucially does not provide information on the *trajectory* to follow over time to reach a target pose. Expert-defined trajectories obviate to this problem providing a length-$`K`$ succession of goal poses $`\tau_K = [p^*_0, p^*_1, \dots p^*_K]`$ for tracking. In practice, trajectories can also be obtained automatically through *motion planning* algorithms, thus avoiding expensive trajectory definition from human experts. However, tracking $`\tau_K`$ via IK can prove prohibitively expensive, as tracking would require $`K`$ resolutions of eq. [ik_problem] (one for each target pose). *Differential* inverse kinematics (diff-IK) complements IK via closed-form solution of a variant of eq. [ik_problem]. Let $`J(q)`$ denote the Jacobian matrix of (partial) derivatives of the FK-function $`f_\text{FK}- \mathcal Q \mapsto \mathcal P`$, such that $`J(q) = \frac{\partial f_{FK}(q)}{\partial q }`$. Then, one can apply the chain rule to any $`p(q) = f_{\text{FK}}(q)`$, deriving $`\dot p = J(q) \dot q`$, and thus finally relating variations in the robot configurations to variations in pose, thereby providing a platform for control. -Given a desired end-effector trajectory $`\dot {p}^*(t)`$ (1) indicating anchor regions in space and (2) how much time to spend in each region, diff-IK finds $`\dot q(t)`$ solving for joints’ *velocities* instead of *configurations*, $`\htmlId{reg_ik_velocity}{\dot q(t) = \arg\min_\nu \; \lVert J(q(t)) \nu - \dot {p}^*(t) \rVert_2^2}`$ +Given a desired end-effector trajectory $`\dot {p}^*(t)`$ (1) indicating anchor regions in space and (2) how much time to spend in each region, diff-IK finds $`\dot q(t)`$ solving for joints’ *velocities* instead of *configurations*, +``` math +\begin{align} +\dot q(t) = \arg\min_\nu \; \lVert J(q(t)) \nu - \dot {p}^*(t) \rVert_2^2 + +\end{align} +``` Unlike eq. [ik_problem], solving for $`\dot q`$ is much less dependent on the environment (typically, variations in velocity are constrained by physical limits on the actuators). Conveniently, eq. [reg_ik_velocity] also often admits the closed-form solution $`\dot q = J(q)^+ \dot {p}^*`$, where $`J^+(q)`$ denotes the Moore-Penrose pseudo-inverse of $`J(q)`$. Finally, discrete-time joint configurations $`q`$ can be reconstructed from joint velocities $`\dot q`$ using forward-integration on the continuous-time joint velocity , $`q_{t+1} = q_t + \Delta t\,\dot q_t`$ for a given $`\Delta t`$, resulting in tracking via diff-IK. @@ -272,7 +279,7 @@ While very effective when a goal trajectory has been well specified, the perform
-r0.3 image +r0.3 image \\ @@ -293,7 +300,7 @@ We point the interested reader to , , and  for extended coverage of FK, IK, di Despite the last 60+ years of robotics research, autonomous robots are still largely incapable of performing tasks at human-level performance in the physical world generalizing across (1) robot embodiments (different manipulators, different locomotion platforms, etc.) and (2) tasks (tying shoe-laces, manipulating a diverse set of objects). While essential in the early development of robotics, the aforementioned methods require significant human expertise to be used in practice, and are typically specific to a particular applicative problem.
- +

<span id="classical-limitations" style="position: absolute;"></span>

Dynamics-based approaches to robotics suffer from several limitations: (1) orchestrating multiple components poses integration challenges; (2) the need to develop custom processing pipelines for the sensing modalities and tasks considered hinders scalability; (3) simplified analytical models of physical phenomena (here friction at the gripper; credits to @antonovaReinforcementLearningPivoting2017) limit real-world performance. Lastly, (4) dynamics-based methods overlook trends in the availability and growth of robotics data.
@@ -327,7 +334,7 @@ TL;DR The need for expensive high-fidelity simulators can be obviated by learnin
- +

<span id="robot-learning-upsides" style="position: absolute;"></span>

Learning-based robotics streamlines perception-to-action by learning a (1) unified high-level controller capable to take (2) high-dimensional, unstructured sensorimotor information. Learning (3) does not require a dynamics model and instead focuses on interaction data, and (4) empirically correlates with the scale of the data used.
@@ -337,7 +344,7 @@ Learning-based techniques for robotics naturally address the limitations present Being a field at its relative nascent stages, no prevalent technique(s) proved distinctly better better in robot learning. Still, two major classes of methods gained prominence- \reinforcement learning (RL)\ and \Behavioral Cloning (BC)\ (Figure [robot-learning-atlas]). In this section, we provide a conceptual overview of applications of the former to robotics, as well as introduce practical examples of how to use RL within `lerobot`. We then introduce the major limitations RL suffers from, to introduce BC techniques in the next sections ([learning-bc-single-sec-learning-bc-generalist]).
- +

<span id="robot-learning-atlas" style="position: absolute;"></span>

Overview of the robot learning methods implemented in lerobot.
@@ -347,7 +354,7 @@ In Figure [robot-learning-atlas] illustrates this categorization graphically, explicitly listing all the robot learning policies currently available in `lerobot`- Action Chunking with Transformers (ACT) @zhaoLearningFineGrainedBimanual2023, Diffusion Policy @chiDiffusionPolicyVisuomotor2024, Vector-Quantized Behavior Transformer (VQ-BeT) @leeBehaviorGenerationLatent2024, $`\pi_0`$ @blackp0VisionLanguageActionFlow2024, SmolVLA @shukorSmolVLAVisionLanguageActionModel2025, Human-in-the-loop Sample-efficient RL (HIL-SERL) @luoPreciseDexterousRobotic2024 and TD-MPC @hansenTemporalDifferenceLearning2022.
- +

<span id="robotics-with-rl-examples" style="position: absolute;"></span>

Examples of two different robotics tasks performed using RL. In the manipulation task (A) an agent learns to reach for a yellow plastic block in its environment, and to put it inside of a box. In the locomotion task (B) an agent learns to move its center of mass sideways without falling.
@@ -359,7 +366,7 @@ Applications of RL to robotics have been long studied, to the point the relation The RL framework @suttonReinforcementLearningIntroduction2018, which we briefly introduce here, has often been used to model robotics problems @koberReinforcementLearningRobotics. RL is a subfield within ML fundamentally concerned with the development of autonomous systems (*agents*) learning how to *continuously behave* in an evolving environment, developing (ideally, well-performing) control strategies (*policies*). Crucially for robotics, RL agents can improve via trial-and-error only, thus entirely bypassing the need to develop explicit models of the problem dynamics, and rather exploiting interaction data only. In RL, this feedback loop (Figure [rl-most-famous-pic]) between actions and outcomes is established through the agent sensing a scalar quantity (*reward*).
- +

<span id="rl-most-famous-pic" style="position: absolute;"></span>

Agent-Environment interaction diagram (image credits to @suttonReinforcementLearningIntroduction2018).
@@ -382,12 +389,14 @@ A length-$`T`$ *trajectory* is the (random) sequence ``` math \htmlId{trajectory_definition}{\tau = (s_0, a_0, r_0, s_1, a_1, r_1, \dots, s_{T-1}, a_{T-1}, r_{T-1}, s_T),} ``` -with per-step rewards defined as $`r_t = r (s_t, a_t, s_{t+1})`$ for ease of notation.Interestingly, assuming both the environment dynamics and conditional distribution over actions given states--the *policy*--to be *Markovian*: -$$ -`\htmlId{dynamics_markovian}{\mathbb P(s_{t+1}\vert s_t, a_t, s_{t-1}, a_{t-1}, \dots s_0, a_0 ) = \mathbb P (s_{t+1}\vert s_t, a_t)\\ - \mathbb P(a_t\vert s_t, a_{t-1}, s_{t-1}, s_0, a_0) = \mathbb P(a_t\vert s_t)}` -$$ - The probability of observing a given trajectory $`\tau`$ factorizes into +with per-step rewards defined as $`r_t = r (s_t, a_t, s_{t+1})`$ for ease of notation.Interestingly, assuming both the environment dynamics and conditional distribution over actions given states--the *policy*--to be *Markovian*: +``` math +\begin{align} +\mathbb P(s_{t+1}\vert s_t, a_t, s_{t-1}, a_{t-1}, \dots s_0, a_0 ) &= \mathbb P (s_{t+1}\vert s_t, a_t) \\ +\mathbb P(a_t\vert s_t, a_{t-1}, s_{t-1}, s_0, a_0) &= \mathbb P(a_t\vert s_t) +\end{align} +``` +The probability of observing a given trajectory $`\tau`$ factorizes into ``` math \htmlId{traj_prob}{\mathbb P(\tau) = \mathbb P (s_0) \prod_{t=0}^{T-1} \mathbb P (s_{t+1}\vert s_t, a_t)\ \mathbb P(a_t\vert s_t).} ``` @@ -396,12 +405,13 @@ Policies $`\mathbb P(a_t\vert s_t)`$ are typically indicated as $`\pi(a_t\vert s ``` math G(\tau) = \sum_{t=0}^{T-1} \gamma^{t} r_t. ``` -In that, agents seek to learn control strategies (*policies*, $`\pi_\theta`$) maximizing the expected return $`\mathbb E_{\tau \sim \pi_\theta} G(\tau)`$. For a given dynamics $`\mathcal D`$--i.e., for a given problem--taking the expectation over the (possibly random) trajectories resulting from acting according to a certain policy provides a direct, goal-conditioned ordering in the space of all the possible policies $`\Pi`$, yielding the (maximization) target $`J : \Pi \mapsto \mathbb R`$ -$$ -`\htmlId{RL-j-function}{J(\pi_\theta) = \mathbb E_{\tau \sim \mathbb P_{\theta; \mathcal D}} [G(\tau)],\\ - \mathbb P_{\theta; \mathcal D} (\tau) = \rho \prod_{t=0}^{T-1} \mathcal D (s_t, a_t, s_{t+1})\ \pi_\theta (a_t\vert s_t).}` -$$ - +In that, agents seek to learn control strategies (*policies*, $`\pi_\theta`$) maximizing the expected return $`\mathbb E_{\tau \sim \pi_\theta} G(\tau)`$. For a given dynamics $`\mathcal D`$--i.e., for a given problem--taking the expectation over the (possibly random) trajectories resulting from acting according to a certain policy provides a direct, goal-conditioned ordering in the space of all the possible policies $`\Pi`$, yielding the (maximization) target $`J : \Pi \mapsto \mathbb R`$ +``` math +\begin{align} + J(\pi_\theta) &= \mathbb E_{\tau \sim \mathbb P_{\theta; \mathcal D}} [G(\tau)], \\ + \mathbb P_{\theta; \mathcal D} (\tau) &= \rho \prod_{t=0}^{T-1} \mathcal D (s_t, a_t, s_{t+1})\ \pi_\theta (a_t\vert s_t). +\end{align} +``` Because in the RL framework the agent is assumed to only be able to observe the environment dynamics and not to intervene on them, [RL-j-function] varies exclusively with the policy followed. In turn, MDPs naturally provide a framework to optimize over the space of the possible behaviors an agent might enact ($`\pi \in \Pi`$), searching for the *optimal policy* $`\pi^* = \arg \max_{\theta} J(\pi_\theta)`$, where $`\theta`$ is the parametrization adopted by the policy set $`\Pi: \pi_\theta \in \Pi, \ \forall \theta`$. Other than providing a target for policy search, $`G(\tau)`$ can also be used as a target to discriminate between states and state-action pairs. Given any state $`s \in \mathcal S`$--e.g., a given configuration of the robot--the *state-value* function ``` math @@ -411,15 +421,18 @@ can be used to discriminate between desirable and undesirable state in terms of ``` math Q_\pi(s,a) = \mathbb E_{\tau \sim \pi} [G (\tau) \big \vert s_0 = s, a_0=a] ``` -Crucially, value functions are interrelated: -$$ -`\htmlId{q-as-v}{Q_\pi(s_t, a_t) = \mathbb{E}_{s_{t+1}\sim \mathbb P(\bullet \vert s_t, a_t)} [r_t + \gamma V_\pi(s_{t+1})]\\ - V_\pi(s_t) = \mathbb E_{a_t\sim \pi(\bullet \vert s_t)} [Q_\pi (s_t, a_t)]}` -$$ - Inducing an ordering over states and state-action pairs under $`\pi`$, value functions are central to most RL algorithms. A variety of methods have been developed in RL as standalone attemps to find (approximate) solutions to the problem of maximizing cumulative reward (Figure [rl-algos-atlas]). +Crucially, value functions are interrelated: +``` math +\begin{align} +Q_\pi(s_t, a_t) &= \mathbb{E}_{s_{t+1}\sim \mathbb P(\bullet \vert s_t, a_t)} [r_t + \gamma V_\pi(s_{t+1})] \\ +V_\pi(s_t) &= \mathbb E_{a_t\sim \pi(\bullet \vert s_t)} [Q_\pi (s_t, a_t)] + +\end{align} +``` +Inducing an ordering over states and state-action pairs under $`\pi`$, value functions are central to most RL algorithms. A variety of methods have been developed in RL as standalone attemps to find (approximate) solutions to the problem of maximizing cumulative reward (Figure [rl-algos-atlas]).
- +

<span id="rl-algos-atlas" style="position: absolute;"></span>

Popular RL algorithms. See @SpinningUp2018 for a complete list of citations.
@@ -435,7 +448,7 @@ First, especially early in training, \actions are typic Second, learning with a limited number of samples remains problematic in RL, \limiting the applicability of RL in real-world robotics due to consequently prohibitive timescales of training\. Even strong algorithms such as SAC @haarnojaSoftActorCriticOffPolicy2018 typically require a large numbers of transitions $`\{ (s_t, a_t, r_t, s_{t+1})\}_{t=1}^N`$. On hardware, generating these data is time-consuming and can even be prohibitive.
- +

<span id="synthetic-vs-real-duck" style="position: absolute;"></span>

Simulated (left) vs. real-world (right) OpenDuck. Discrepancies in the simulation dynamics (reality gap) pose risks to policy transfer.
@@ -443,7 +456,7 @@ Second, learning with a limited number of samples remains problematic in RL, \[synthetic-vs-real-duck]). *Domain randomization* (DR) is a popular technique to overcome the reality gap, consisting in randomizing parameters of the simulated environment during training, to induce robustness to specific disturbances. In turn, DR is employed to increase the diversity of scenarios over the course of training, improving on the chances sim-to-real transfer @akkayaSolvingRubiksCube2019, @antonovaReinforcementLearningPivoting2017, @jiDribbleBotDynamicLegged2023. In practice, DR is performed further parametrizing the *simulator*’s dynamics $`\mathcal D \equiv \mathcal D_\xi`$ with a *dynamics* (random) vector $`\xi`$ drawn an arbitrary distribution, $`\xi \sim \Xi`$. Over the course of training--typically at each episode’s reset--a new $`\xi`$ is drawn, and used to specify the environment’s dynamics for that episode. For instance, one could decide to randomize the friction coefficient of the surface in a locomotion task (Figure [ducks-on-terrains]), or the center of mass of an object for a manipulation task.
- +

<span id="ducks-on-terrains" style="position: absolute;"></span>

The same locomotion task can be carried out in different (simulated) domains (exemplified by the difference in terrains) at training time, resulting to increased robustness over diverse environment dynamics.
@@ -479,15 +492,17 @@ Q_{i+1}(s_t, a_t) \leftarrow \mathbb E_{s_{t+1} \sim \mathbb P(\bullet \vert s_t ``` Then, one can derive the (ideally, near-optimal) policy by explicitly maximizing over the action space the final (ideally, near-optimal) estimate $`Q_K \approx Q^*`$ at each timestep. In fact, under certain assumptions on the MDP considered, $`Q_K \to Q^* \, \text{as } K \to \infty`$. -Effective in its early applications to small-scale discrete problems and theoretically sound, vanilla Q-learning was found complicated to scale to large $`\mathcal S\times \mathcal A`$ problems, in which the storing of $`Q : \mathcal S\times \mathcal A\mapsto \mathbb R`$ alone might result prohibitive. Also, vanilla Q-learning is not directly usable for *continuous*, unstructured state-action space MPDs, such as those considered in robotics. In their seminal work on *Deep Q-Learning* (DQN), @mnihPlayingAtariDeep2013 propose learning Q-values using deep convolutional neural networks, thereby accomodating for large and even unstructured *state* spaces. DQN parametrizes the Q-function using a neural network with parameters $`\theta`$, updating the parameters by sequentially minimizing the expected squared temporal-difference error (TD-error, $`\delta_i`$): -$$ -`\htmlId{dqn-loss}{\mathcal L(\theta_i) = \mathbb E_{(s_t, a_t) \sim \chi(\bullet)} +Effective in its early applications to small-scale discrete problems and theoretically sound, vanilla Q-learning was found complicated to scale to large $`\mathcal S\times \mathcal A`$ problems, in which the storing of $`Q : \mathcal S\times \mathcal A\mapsto \mathbb R`$ alone might result prohibitive. Also, vanilla Q-learning is not directly usable for *continuous*, unstructured state-action space MPDs, such as those considered in robotics. In their seminal work on *Deep Q-Learning* (DQN), @mnihPlayingAtariDeep2013 propose learning Q-values using deep convolutional neural networks, thereby accomodating for large and even unstructured *state* spaces. DQN parametrizes the Q-function using a neural network with parameters $`\theta`$, updating the parameters by sequentially minimizing the expected squared temporal-difference error (TD-error, $`\delta_i`$): +``` math +\begin{align} +\mathcal L(\theta_i) &= \mathbb E_{(s_t, a_t) \sim \chi(\bullet)} \big[ (\underbrace{y_i - Q_{\theta_i}(s_t, a_t)}_{\delta_i})^2 - \big],\\ - y_i = \mathbb E_{s_{t+1} \sim \mathbb P(\bullet \vert s_t, a_t)} \big[ r_t + \gamma \max_{a_t\in \mathcal A} Q_{\theta_{i-1}} (s_{t+1}, a_{t+1}) \big],}` -$$ - Where $`\chi`$ represents a behavior distribution over state-action pairs. Crucially, $`\chi`$ can in principle be different from the policy being followed, effectively allowing to reuse prior data stored in a *replay buffer* in the form of $`(s_t, a_t, r_t, s_{t+1})`$ transitions, used to form the TD-target $`y_i`$, TD-error $`\delta_i`$ and loss function [dqn-loss] via Monte-Carlo (MC) estimates. + \big], \\ + y_i &= \mathbb E_{s_{t+1} \sim \mathbb P(\bullet \vert s_t, a_t)} \big[ r_t + \gamma \max_{a_t\in \mathcal A} Q_{\theta_{i-1}} (s_{t+1}, a_{t+1}) \big], +\end{align} +``` +Where $`\chi`$ represents a behavior distribution over state-action pairs. Crucially, $`\chi`$ can in principle be different from the policy being followed, effectively allowing to reuse prior data stored in a *replay buffer* in the form of $`(s_t, a_t, r_t, s_{t+1})`$ transitions, used to form the TD-target $`y_i`$, TD-error $`\delta_i`$ and loss function [dqn-loss] via Monte-Carlo (MC) estimates. While effective in handling large, unstructured state spaces for discrete action-space problems, DQN application’s to continous control problems proved challenging. Indeed, in the case of high-capacity function approximators such as neural networks, solving $`\max_{a_t \in \mathcal A} Q_\theta(s_t, a_t)`$ at each timestep is simply unfeasible due to the (1) continous nature of the action space ($`\mathcal A\subset \mathbb R^n`$ for some $`n`$) and (2) impossibility to express the find a cheap (ideally, closed-form) solution to $`Q_\theta`$.  @silverDeterministicPolicyGradient2014 tackle this fundamental challenge by using a *deterministic* function of the state $`s_t`$ as policy, $`\mu_\phi(s_t) = a_t`$, parametrized by $`\phi`$. Thus, policies can be iteratively refined updating $`\phi`$ along the direction: ``` math @@ -499,7 +514,13 @@ Provably, maximizing the discounted cumulative reward, while acting as randomly as possible\. MaxEnt RL @haarnojaReinforcementLearningDeep2017 has proven particularly robust thanks to the development of diverse behaviors, incentivized by its entropy-regularization formulation. In that, MaxEnt revisits the RL objective $`J (\pi)`$ to specifically account for the policy entropy, $`\htmlId{J-soft}{J(\pi) = \sum_{t=0}^T \mathbb{E}_{(s_t, a_t) \sim \chi} [r_t + \alpha \mathcal H(\pi (\bullet \vert s_t))]}`$ This modified objective results in the *soft* TD-target: +Soft Actor-Critic (SAC) @haarnojaSoftActorCriticOffPolicy2018 is a derivation of DDPG in the max-entropy (MaxEnt) RL framework, in which RL agents are tasked with \maximizing the discounted cumulative reward, while acting as randomly as possible\. MaxEnt RL @haarnojaReinforcementLearningDeep2017 has proven particularly robust thanks to the development of diverse behaviors, incentivized by its entropy-regularization formulation. In that, MaxEnt revisits the RL objective $`J (\pi)`$ to specifically account for the policy entropy, +``` math +\begin{align} + J(\pi) &= \sum_{t=0}^T \mathbb{E}_{(s_t, a_t) \sim \chi} [r_t + \alpha \mathcal H(\pi (\bullet \vert s_t))] +\end{align} +``` +This modified objective results in the *soft* TD-target: ``` math \htmlId{soft-td-target}{y_i = \mathbb E_{s_{t+1} \sim \mathbb P( \bullet \vert s_t, a_t)} [r_t + \gamma \left( Q_{\theta_{i-1}} (s_{t+1}, a_{t+1}) - \alpha \log \pi_\phi(a_{t+1} \vert s_{t+1}) \right)], \quad a_{t+1} \sim \pi_\phi(\bullet \vert s_t)} ``` @@ -522,7 +543,7 @@ Despite the possibility to leverage offline data for learning, the effectiveness Lastly, in order to improve on the robustness of their approach to different goals while maintaing practical scalability, @luoSERLSoftwareSuite2025 introduced a modified state and action space, expressing proprioperceptive configurations $`q`$ and actions $`\dot q`$ in the frame of end-effector pose at $`t=0`$. Randomizing the initial pose of the end-effector ($`s_0`$),@luoSERLSoftwareSuite2025 achieved a similar result to that of having to manually randomize the environment at every timestep, but with the benefit of maintaining the environment in the same condition across multiple training episodes, achieving higher scalability of their method thanks to the increased practicality of their approach.
- +

<span id="hil-serl-blocks" style="position: absolute;"></span>

(A) HIL-SERL allows for real-world training of high performance RL agents by building on top advancements presented by of SAC, RLPD and SERL. (B) Example of human intervention during a HIL-SERL training process on a SO-100.
@@ -564,7 +585,7 @@ TL;DR Behavioral Cloning provides a natural platform to learn from real-world in
- +

<span id="ch4-bc-trajectories" style="position: absolute;"></span>

(A) Average (with standard deviation) evolution of the actuation levels over the first 5 recorded episodes in lerobot/svla_so101_pickplace. Proprioperceptive state provide invaluable to determine the robot’s state during an episode. (B) Camera frames are also recorded alongside measurements on the robot’s state, capturing information about the robot’s interaction with its environment.
@@ -574,7 +595,7 @@ Learning from human demonstrations provides a pragmatic alternative to the reinf Formally, let $`\mathcal D = \{ \tau^{(i)} \}_{i=1}^N`$ be a set of expert trajectories, with $`\tau^{(i)} = \{(o_t^{(i)}, a_t^{(i)})\}_{t=0}^{T_i}`$ representing the $`i`$-th trajectory in $`\mathcal D`$, $`o_t \in \mathcal O`$ denoting observations (e.g., images and proprioception altogether), and $`a_t \in \mathcal A`$ the expert actions. Typically, observations $`o \in \mathcal O`$ consist of both image and proprioperceptive information, while actions $`a \in \mathcal A`$ represent control specifications for the robot to execute, e.g. a joint configuration. Note that differently from Section [learning-rl], in the imitation learning context $`\mathcal D`$ denotes an offline dataset collecting $`N`$ length-$`T_i`$ reward-free (expert) human trajectories $`\tau^{(i)}`$, and *not* the environment dynamics. Similarily, in this section $`\tau^{(i)}`$ represent a length-$`T_i`$ trajectory of observation-action pairs, which crucially *omits entirely any reward* information. Figure [ch4-bc-trajectories] graphically shows trajectories in terms of the average evolution of the actuation on the 6 joints over a group of teleoperated episodes for the SO-100 manipulator. Notice how proprioperceptive states are captured jointly with camera frames over the course of the recorded episodes, providing a unified high-frame rate collection of teleoperation data. Figure [ch4-observation-action-mapping] shows $`(o_t, a_t)`$-pairs for the same dataset, with the actions performed by the human expert illustrated just alongside the corresponding observation. In principle, (expert) trajectories $`\tau^{(i)}`$ can have different lengths since demonstrations might exhibit multi-modal strategies to attain the same goal, resulting in possibly multiple, different behaviors.
- +

<span id="ch4-observation-action-mapping" style="position: absolute;"></span>

Sample observations and action pairs over the course of a given trajectory recorded in lerobot/svla_so101_pickplace. Observations, comprising of both proprioperceptive and visual information, are recorded alongside the configuration of a second, leader robot controlled by a human expert, providing complete information for regressing actions given observation.
@@ -590,7 +611,7 @@ Typically, the expert’s joint observation-action distribution $`p: \mathcal O\ Despite the inherent challenges of learning on non-i.i.d. data, the BC formulation affords several operational advantages in robotics. First, training happens offline and typically uses expert human demonstration data, hereby severily limiting exploration risks by preventing the robot from performing dangerous actions altogether. Second, reward design is entirely unnecessary in BC, as demonstrations already reflect human intent and task completion. This also mitigates the risk of misalignment and specification gaming (*reward hacking*), otherwise inherent in purely reward-based RL @heessEmergenceLocomotionBehaviours2017. Third, because expert trajectories encode terminal conditions, success detection and resets are implicit in the dataset. Finally, BC scales naturally with growing corpora of demonstrations collected across tasks, embodiments, and environments. However, BC can in principle only learn behaviors that are, at most, as good as the one exhibited by the demonstrator, and thus critically provides no mitigation for the suboptimal decision making that might be enaced by humans. Still, while problematic in sequential-decision making problems for which expert demonstrations are not generally available--data migth be expensive to collect, or human performance may be inherently suboptimal--many robotics applications benefit from relative cheap pipelines to acquire high-quality trajectories generated by humans, thus justifying BC approaches.
- +

<span id="ch4-issues-with-bc" style="position: absolute;"></span>

Point-wise policies suffer from limitations due to (A) covariate shifts and poor approximation of (B) multimodal demonstrations. (A) Initially small errors may drive the policy out of distribution, incuring in a vicious circle ultimately resulting in failure. (B) Both modes of reaching for a target object in a scene, either left or right-first, are equally as good and thus equally as likely to be present in a dataset of human demonstrations, ultimately resulting in multimodal demonstrations.
@@ -604,7 +625,7 @@ Generative Models (GMs) aim to learn the stochastic process underlying the very #### Variational Auto-Encoders
- +

<span id="ch4-task-effect-on-pairs" style="position: absolute;"></span>

Intuitively, latent variable in a single latent model may contain information regarding the task being performed, which directly results in the likelihood of the same observation-action pair being different for two different tasks. When (A) picking a block the likelihood of a wide gripper’s opening should be higher than narrower one, while it should be the opposite when (B) pushing the block.
@@ -616,49 +637,60 @@ A common inductive bias used in GM posits samples $`(o,a)`$ are influenced from Intuitively, in the case of observation-action pairs $`(o, a)`$ for a robotics application, $`z`$ could be some high level representation of the underlying task being performed by the human demonstrator. In such case, treating $`p(o,a)`$ as a marginalization over $`\text{supp}({Z})`$ of the complete joint distribution $`p(o,a,z)`$ natively captures the effect different tasks have on the likelihood of observation-action pairs. Figure [ch4-task-effect-on-pairs] graphically illustrates this concept in the case of a (A) picking and (B) pushing task, for which, nearing the target object, the likelihood of actions resulting in opening the gripper--the higher $`q_6`$, the wider the gripper’s opening--should intuitively be (A) high or (B) low, depending on the task performed. While the latent space $`Z`$ typically has a much richer structure than the set of all actual tasks performed, [BC-latent-variable] still provides a solid framework to learn joint distribution conditioned on unobservable yet relevant factors. Figure [ch4-latent-variable-model] represents this framework of latent-variable for a robotics application- the true, $`z`$-conditioned generative process on assigns *likelihood* $`p((o,a) \vert z)`$ to the single $`(o,a)`$-pair. Using Bayes’ theorem, one can reconstruct the *posterior* distribution on $`\text{supp}({Z})`$, $`q_\theta(z \vert o,a)`$ from the likelihood $`p_\theta(o,a \vert z)`$, *prior* $`p_\theta(z)`$ and *evidence* $`p_\theta(o,a)`$. VAEs approximate the latent variable model presented in [BC-latent-variable]) using an *approximate posterior* $`q_\phi(z \vert o,a)`$ while regressing parameters for a parametric likelihood, $`p_\theta(o,a \vert z)`$ (Figure [ch4-latent-variable-model]).
- +

<span id="ch4-latent-variable-model" style="position: absolute;"></span>

(A) The latent variable model in a robotics application regulates influence between observed (o, a) variables and an unobservable latent variable. (B) VAEs approximate exact latent variable models by means of variational inference.
-Given a dataset $`\mathcal D`$ consisting of $`N`$ i.i.d. observation-action pairs, the log-likelihood of all datapoints under $`\theta`$ (in Bayesian terms, the *evidence* $`p_\theta(\mathcal D)`$) can thus be written as: -$$ -`\htmlId{evidence-definition-1}{\log p_\theta(\mathcal D) = \log \sum_{i=0}^N p_\theta ((o,a)_i)\\ - = \log \sum_{i=0}^N \int_{\text{supp}({Z})} p_\theta((o,a)_i \vert z) p(z)\\ - = \log \sum_{i=0}^N \int_{\text{supp}({Z})} \frac{q_\theta(z \vert (o,a)_i)}{q_\theta(z \vert (o,a)_i)} \cdot p_\theta((o,a)_i \vert z) p(z)\\ - = \log \sum_{i=0}^N \mathbb E_{z \sim p_\theta(\bullet \vert (o,a)_i)} [\frac{p(z)}{q_\theta(z \vert (o,a)_i)} \cdot p_\theta((o,a)_i \vert z)],}` -$$ - where we used [BC-latent-variable] in [evidence-definition-1], multiplied by $`1 = \frac{q_\theta(z \vert (o,a)_i)}{q_\theta(z \vert (o,a)_i)}`$ in [evidence-definition-2], and used the definition of expected value in [evidence-definition]. +Given a dataset $`\mathcal D`$ consisting of $`N`$ i.i.d. observation-action pairs, the log-likelihood of all datapoints under $`\theta`$ (in Bayesian terms, the *evidence* $`p_\theta(\mathcal D)`$) can thus be written as: +``` math +\begin{align} + \log p_\theta(\mathcal D) &= \log \sum_{i=0}^N p_\theta ((o,a)_i) \\ + &= \log \sum_{i=0}^N \int_{\text{supp}({Z})} p_\theta((o,a)_i \vert z) p(z) \\ + &= \log \sum_{i=0}^N \int_{\text{supp}({Z})} \frac{q_\theta(z \vert (o,a)_i)}{q_\theta(z \vert (o,a)_i)} \cdot p_\theta((o,a)_i \vert z) p(z) \\ + &= \log \sum_{i=0}^N \mathbb E_{z \sim p_\theta(\bullet \vert (o,a)_i)} [\frac{p(z)}{q_\theta(z \vert (o,a)_i)} \cdot p_\theta((o,a)_i \vert z)], +\end{align} +``` +where we used [BC-latent-variable] in [evidence-definition-1], multiplied by $`1 = \frac{q_\theta(z \vert (o,a)_i)}{q_\theta(z \vert (o,a)_i)}`$ in [evidence-definition-2], and used the definition of expected value in [evidence-definition]. In the special case where one assumes distributions to be tractable, $`p_\theta (\mathcal D)`$ is typically tractable too, and $`\max_\theta \log p_\theta(\mathcal D)`$ provides a natural target for (point-wise) infering the unknown parameters $`\theta`$ of the generative model. Unfortunately, [evidence-definition] is rarely tractable when the distribution $`p`$ is modeled with approximators such as neural networks, especially for high-dimensional, unstructured data. In their seminal work on Variational Auto-Encoders (VAEs), @kingmaAutoEncodingVariationalBayes2022 present two major contributions to learn complex latent-variable GMs on unstructured data, proposing (1) a tractable, variational lower-bound to [evidence-definition] as an optimization target to jointly learn likelihood and posterior and (2) high-capacity function approximators to model the likelihood $`p_\theta(o,a\vert z)`$ and (approximate) posterior distribution $`q_\phi(z \vert o,a) \approx q_\theta(z \vert o,a)`$. -In particular, the lower bound on [evidence-definition] (Evidence LOwer Bound, *ELBO*) can be derived from [evidence-definition] applying Jensen’s inequality--$`\log \mathbb{E}[\bullet] \geq \mathbb{E} [\log (\bullet)]`$--yielding: -$$ -`\htmlId{ELBO-intractable}{\log p_\theta(\mathcal D) \geq \sum_{i=0}^{N} \left( +In particular, the lower bound on [evidence-definition] (Evidence LOwer Bound, *ELBO*) can be derived from [evidence-definition] applying Jensen’s inequality--$`\log \mathbb{E}[\bullet] \geq \mathbb{E} [\log (\bullet)]`$--yielding: +``` math +\begin{align} + \log p_\theta(\mathcal D) &\geq \sum_{i=0}^{N} \left( \mathbb{E}_{z \sim p_\theta(\cdot \vert (o,a)_i)} \big[ \log p_\theta((o,a)_i \vert z) \big] + \mathbb{E}_{z \sim p_\theta(\cdot \vert (o,a)_i)} [\log \left( \frac{p(z)}{q_\theta(z \vert (o,a)_i)} \right)] - \right)\\ - = \sum_{i=0}^{N} \left( + \right) \\ + &= \sum_{i=0}^{N} \left( \mathbb{E}_{z \sim p_\theta(\cdot \vert (o,a)_i)} \big[ \log p_\theta((o,a)_i \vert z) \big] - \text{D}_{\text{KL}}\big[ q_\theta(z \vert (o,a)_i) \Vert p(z) \big] - \right)}` -$$ - The true, generally intractable posterior $`p_\theta (z \vert o,a)`$ prevents computing both the expectation and KL divergence terms in [ELBO-intractable], and therefore @kingmaAutoEncodingVariationalBayes2022 propose deriving the ELBO using an *approximate* posterior $`q_\phi(z \vert o,a)`$, resulting in the final, tractable ELBO objective, $`\htmlId{ELBO}{\text{ELBO}_{\mathcal D}(\theta, \phi) = \sum_{i=0}^{N} \left( + \right) +\end{align} +``` +The true, generally intractable posterior $`p_\theta (z \vert o,a)`$ prevents computing both the expectation and KL divergence terms in [ELBO-intractable], and therefore @kingmaAutoEncodingVariationalBayes2022 propose deriving the ELBO using an *approximate* posterior $`q_\phi(z \vert o,a)`$, resulting in the final, tractable ELBO objective, +``` math +\begin{align} +\text{ELBO}_{\mathcal D}(\theta, \phi) = \sum_{i=0}^{N} \left( \mathbb{E}_{z \sim q_\phi(\cdot \vert (o,a)_i)} \big[ \log p_\theta((o,a)_i \vert z) \big] - \text{D}_{\text{KL}}\big[ q_\phi(z \vert (o,a)_i) \Vert p(z) \big] - \right)}`$ From Jensen’s inequality, maximizing ELBO results in maximizing the log-likelihood of the data too, thus providing a natural, tractable optimization target. Indeed, expectations can be estimated using MC estimates from the learned distributions in [ELBO], while the KL-divergence term can typically be computed in closed-form (1) modeling $`q_\phi`$ as a Gaussian $`q_\phi(z \vert o,a) = \mathcal N\big(\mu_\phi(o,a), \Sigma_\phi(o,a) \big)`$ and (2) imposing a standard Gaussian prior on the latent space, $`p(z) = \mathcal N(\mathbf{0}, \mathbf{I})`$. + \right) + +\end{align} +``` +From Jensen’s inequality, maximizing ELBO results in maximizing the log-likelihood of the data too, thus providing a natural, tractable optimization target. Indeed, expectations can be estimated using MC estimates from the learned distributions in [ELBO], while the KL-divergence term can typically be computed in closed-form (1) modeling $`q_\phi`$ as a Gaussian $`q_\phi(z \vert o,a) = \mathcal N\big(\mu_\phi(o,a), \Sigma_\phi(o,a) \big)`$ and (2) imposing a standard Gaussian prior on the latent space, $`p(z) = \mathcal N(\mathbf{0}, \mathbf{I})`$. An intuitive explanation of the learning dynamics of VAEs can be given considering the equivalent case of *minimizing the negative ELBO*, which admits a particularly interpretable factorization - -$$ -`\htmlId{VAE-min-neg-ELBO}{\min_{\theta, \phi} - \text{ELBO}_{\mathcal (o,a) \sim \mathcal D}(\theta, \phi) = \min_{\theta, \phi}\mathbf{L^{\text{rec}}}(\theta) + \mathbf{L^{\text{reg}}}(\phi)\\ - \mathbf{L^{\text{rec}}}(\theta) = \mathbb{E}_{z \sim q_\phi(\cdot \vert o,a} \big[ \log p_\theta(o,a \vert z) \big]\\ - \mathbf{L^{\text{reg}}}(\phi) = \text{D}_{\text{KL}}\big[ q_\phi(z \vert o,a) \Vert p(z) \big]}` -$$ - +``` math +\begin{align} +\min_{\theta, \phi} - \text{ELBO}_{\mathcal (o,a) \sim \mathcal D}(\theta, \phi) &= \min_{\theta, \phi}\mathbf{L^{\text{rec}}}(\theta) + \mathbf{L^{\text{reg}}}(\phi) \\ +\mathbf{L^{\text{rec}}}(\theta) &= \mathbb{E}_{z \sim q_\phi(\cdot \vert o,a} \big[ \log p_\theta(o,a \vert z) \big] \\ +\mathbf{L^{\text{reg}}}(\phi) &= \text{D}_{\text{KL}}\big[ q_\phi(z \vert o,a) \Vert p(z) \big] +\end{align} +``` For any given $`(o,a)`$ pair, the expected value term of [VAE-Lrec] is typically computed via MC estimates, resulting in ``` math @@ -672,15 +704,17 @@ Indeed, it is very common in practice to approximate from the learned likelihood #### Diffusion Models -VAEs approximate probability distributions via a *single* latent variable model, assuming the underlying unknown distribution can be factored according to [BC-latent-variable], and solve the variational inference problem of jointly learning the likelihood $`p_\theta`$ and (approximate) posterior $`q_\phi`$ for such model. In that, the unknown data distribution $`p(o,a)`$ is effectively approximated via $`\int_Z p(z) p_\theta(o,a \vert z)`$, and the underlying generative process reproduced by (1) sampling a latent variable and (2) learning to decode it into a (ideally) high-likelihood sample under the (unknown) $`p(o,a)`$. Diffusion Models (DMs) @hoDenoisingDiffusionProbabilistic2020 are another class of GMs which treat the similar problem of approximating an underlying unknown data distribution--*variational inference*--by *partially* extending VAEs to the case where *multiple* latent variables influence each other and the generative process underlying $`o,a`$ itself. In particular, DMs posit the generative process can be decomposed to a series of piece-wise (Markovian) interactions between (latent) variables (Figure [ch4-many-latents]), resulting in -$$ -`\htmlId{BC-multi-latent-model-1}{p(\underbrace{o,a}_{= z_0}) = \int_{\text{supp}({Z_0})} \int_{\text{supp}({Z_1})} \ldots \int_{\text{supp}({Z_T})} p(z_0, z_1, \dots z_T)\\ - p(z_0, z_1, \dots z_T) = p(z_T) \prod_{t=0}^{T} p(z_{t-1} \vert z_t),}` -$$ - where we explicitly showed the marginalization over the multiple latents in [BC-multi-latent-model-1], and used the law of conditional probability and Markov property in [BC-multi-latent-model-2]. +VAEs approximate probability distributions via a *single* latent variable model, assuming the underlying unknown distribution can be factored according to [BC-latent-variable], and solve the variational inference problem of jointly learning the likelihood $`p_\theta`$ and (approximate) posterior $`q_\phi`$ for such model. In that, the unknown data distribution $`p(o,a)`$ is effectively approximated via $`\int_Z p(z) p_\theta(o,a \vert z)`$, and the underlying generative process reproduced by (1) sampling a latent variable and (2) learning to decode it into a (ideally) high-likelihood sample under the (unknown) $`p(o,a)`$. Diffusion Models (DMs) @hoDenoisingDiffusionProbabilistic2020 are another class of GMs which treat the similar problem of approximating an underlying unknown data distribution--*variational inference*--by *partially* extending VAEs to the case where *multiple* latent variables influence each other and the generative process underlying $`o,a`$ itself. In particular, DMs posit the generative process can be decomposed to a series of piece-wise (Markovian) interactions between (latent) variables (Figure [ch4-many-latents]), resulting in +``` math +\begin{align} + p(\underbrace{o,a}_{= z_0}) &= \int_{\text{supp}({Z_0})} \int_{\text{supp}({Z_1})} \ldots \int_{\text{supp}({Z_T})} p(z_0, z_1, \dots z_T) \\ + p(z_0, z_1, \dots z_T) &= p(z_T) \prod_{t=0}^{T} p(z_{t-1} \vert z_t), +\end{align} +``` +where we explicitly showed the marginalization over the multiple latents in [BC-multi-latent-model-1], and used the law of conditional probability and Markov property in [BC-multi-latent-model-2].
- +

<span id="ch4-many-latents" style="position: absolute;"></span>

HMLV models posit the data generation process is influenced by a stack of Markov-dependent latent variables, with samples from the posterior distribution being progressively higher up in the hierarchy.
@@ -689,19 +723,21 @@ Similarily to VAEs, providing an exact interpretation for the latent variables i Just like VAEs, DMs attemp to learn to reproduce an underlying data distribution $`p (o,a)`$ given a collection of i.i.d. samples approximating the model posited to have generated the data in the first place ( [BC-multi-latent-model-1]). Similarily to VAEs, DMs approximate the process of sampling from the unknown $`p(o,a)`$ (1) sampling from an easy-to-sample distribution (e.g., Gaussian) and (2) learning to reconstruct high-likelihood samples under the unknown distribution. However, in stark contrast with VAEs, the easy-to-sample distribution contains *no mutual information* regarding the data distribution $`p(o,a)`$. Crucially, as no information from the sample $`(o,a)`$ (denoted as $`z_0 \equiv (o,a)`$ for the sake of notation) is assumed to be propagated throughout the chain of latents, the posterior $`q(z_t \vert z_{t-1})`$ assumes a relatively amicable structure in DMs, reducing complexity. The *true* likelihood $`p(z_{t-1} \vert z_t)`$ is instead typically approximated using the parametrization $`p_\theta (z_{t-1} \vert z_t)`$. In that, the information contained in the unknwon data distribution is *reconstructed* via a process in which samples from a fixed distribution are turned into (ideally) high-likelihood samples under $`p(o,a)`$--a process referred to as *denoising*. -Under such model, we can express the log-likelihood of an arbitrary sample as[^4] -$$ -`\htmlId{diffusion-likelihood}{\log p_\theta (\underbrace{o,a}_{= z_0}) = - \mathbb{E}_{z_1 \sim q(\bullet \vert z_0)} \log p_\theta (z_0 \vert z_1) -\\ - \mathbb{E}_{z_{T-1} \sim q(\bullet \vert z_0)} \big[ \text{D}_{\text{KL}}(q(z_T \vert z_{T-1}) \Vert p(z_T) ) \big] - \notag\\ - \sum_{t=1}^{T-1} \mathbb{E}_{(z_{t-1}, z_{t+1}) \sim q(\bullet \vert z_0)} \big[ \text{D}_{\text{KL}}(q(z_t \vert z_{t-1}) \Vert p_\theta(z_t \vert z_{t-1}) ) \big], \notag}` -$$ - providing an optimization target in the form of $`\max_\theta \log p_\theta (\mathcal D)`$. +Under such model, we can express the log-likelihood of an arbitrary sample as[^4] +``` math +\begin{align} + \log p_\theta (\underbrace{o,a}_{= z_0}) = + &\mathbb{E}_{z_1 \sim q(\bullet \vert z_0)} \log p_\theta (z_0 \vert z_1) - \\ + &\mathbb{E}_{z_{T-1} \sim q(\bullet \vert z_0)} \big[ \text{D}_{\text{KL}}(q(z_T \vert z_{T-1}) \Vert p(z_T) ) \big] - \notag \\ + &\sum_{t=1}^{T-1} \mathbb{E}_{(z_{t-1}, z_{t+1}) \sim q(\bullet \vert z_0)} \big[ \text{D}_{\text{KL}}(q(z_t \vert z_{t-1}) \Vert p_\theta(z_t \vert z_{t-1}) ) \big], \notag +\end{align} +``` +providing an optimization target in the form of $`\max_\theta \log p_\theta (\mathcal D)`$. In their seminal work on using DMs for variational inference, @hoDenoisingDiffusionProbabilistic2020 introduce major contributions regarding solving $`\min_\theta -\log p_\theta(o,a)`$. In particular, @hoDenoisingDiffusionProbabilistic2020 exclusively adopt a fixed *Gaussian* posterior in the form of $`q(z_t \vert z_{t-1}) = \mathcal{N}(\sqrt{1-\beta_t}z_{t-1}, \beta_t \mathbf I)`$. The choice of adopting Gaussians has profound implications on the generative process modeled. Indeed, under the (mild) assumption that the variance is sufficiently small $`\beta_t \leq \eta, \eta \in \mathbb R^+`$, @sohl-dicksteinDeepUnsupervisedLearning2015 proved that the likelihood $`p(z_{t-1} \vert z_t)`$ is Gaussian as well, which allows for the particularly convenient parametrization of the approximate likelihood $`p_\theta (x_{t-1} \vert x_t) = \mathcal N(\mu_\theta(x_t, t), \Sigma_\theta(x_t,t)), \ t \in [1,T]`$, as well as for closed-form tractability of the KL-divergence terms in [diffusion-likelihood]. Further, the posterior’s structure also enables an analytical description for the distribution of the $`t`$-th latent variable, $`q(z_t \vert z_0) = \mathcal N (\sqrt{\bar{\alpha}_t}z_0, (1-\bar{\alpha}_t) \mathbf{I})`$, with $`\alpha_t = 1-\beta_t, \ \bar \alpha_t = \prod_{k=1}^t \alpha_k`$, which conveniently prevents iterative posterior sampling.
- +

<span id="diffusion-robot-actions" style="position: absolute;"></span>

DMs iteratively corrupt samples (left) from an unknown distribution into a quasi-standard Gaussian (center), learning the displacement field (right) that permits to reconstruct samples from the unknown target distribution by iteratively denoising samples of a tractable, easy-to-sample distribution.
@@ -709,30 +745,43 @@ In their seminal work on using DMs for variational inference, @hoDenoisingDiffu Finally, adopting Gaussian posteriors permits a particularly pleasing interpretation of the dynamics of training DMs @permenterInterpretingImprovingDiffusion2024. By using Gaussian posteriors, the hierarchical latent variables effectively lose increasingly more information circa the original (unknown) distribution’s sample, $`z_0`$, increasingly distributing according to a standard Gaussian and thus containing no information at all (Figure [diffusion-robot-actions]). Figure [diffusion-robot-actions] illustrates this procedure on a simplified, bidimensional observation-action distribution, where we considered $`o=q_2`$ and $`a=q^h_2`$, with $`q_2`$ representing the robot’s *elbow flex* actuation and $`q^h_2`$ the human teleoperator’s robot elbow flex.
- +

<span id="ch4-action-vs-observation-distribution" style="position: absolute;"></span>

A joint action-observation distribution, in the simplified case where the observation is the elbow-flex actuation in a SO-100, and the action is the recorded position for the same joint in the teleoperator arm. The motion recorded being teleoperated, the points distribute along a the diagonal.
-Because the recorded behavior is teleoperated, measurements mostly distribute along the line $`a = o + \eta, \eta \sim N(0,1)`$, with $`\eta`$-variability accouting for minor control inconsistencies (Figure [ch4-action-vs-observation-distribution]). Using Gaussian posteriors--i.e., adding Gaussian noise--effectively simulates a *Brownian motion* for the elements in the distribution’s support (in Figure [diffusion-robot-actions], $`\mathcal O\times \mathcal A`$), whereby information *diffuses away* from the samples, and comparing the diffused samples to the original data points one can derive an estimate of the total displacement induced by diffusion. Under the only assumption that the likelihood of the diffused samples is low under the original unknown data distribution, then one can effectively approximate the unkwown distribution by learning to *reverse* such displacement. This key intuition allows to write a simplified training objective: $`\htmlId{diffusion-simplified-loss}{\mathcal L(\theta) = \mathbb{E}_{t, z_0, \epsilon} \big[ +Because the recorded behavior is teleoperated, measurements mostly distribute along the line $`a = o + \eta, \eta \sim N(0,1)`$, with $`\eta`$-variability accouting for minor control inconsistencies (Figure [ch4-action-vs-observation-distribution]). Using Gaussian posteriors--i.e., adding Gaussian noise--effectively simulates a *Brownian motion* for the elements in the distribution’s support (in Figure [diffusion-robot-actions], $`\mathcal O\times \mathcal A`$), whereby information *diffuses away* from the samples, and comparing the diffused samples to the original data points one can derive an estimate of the total displacement induced by diffusion. Under the only assumption that the likelihood of the diffused samples is low under the original unknown data distribution, then one can effectively approximate the unkwown distribution by learning to *reverse* such displacement. This key intuition allows to write a simplified training objective: +``` math +\begin{align} + + \mathcal L(\theta) = \mathbb{E}_{t, z_0, \epsilon} \big[ \Vert \epsilon - \epsilon_\theta(\sqrt{\bar \alpha_t} z_0 + \epsilon \sqrt{1 - \bar \alpha_t}, t) \Vert^2 \big], \quad t \sim \mathcal{U}(\{1,\dots,T\}), \quad z_0 \sim \mathcal{D}, \quad - \epsilon \sim \mathcal{N}(\mathbf{0},\mathbf{I}).}`$ + \epsilon \sim \mathcal{N}(\mathbf{0},\mathbf{I}). +\end{align} +``` In this simplified (minimization) objective, the optimization process differs from [diffusion-likelihood] in that, rather than maxizing $`p_\theta`$ directly, the parameters $`\theta`$ of the pairwise likelihood $`p_\theta(z_{t-1} \vert z_t)`$ are adjusted to *predict the total displacement* $`\epsilon`$ for a randomly long ($`t \sim \mathcal{U}(\{1,\dots,T\}`$ )) diffusion process starting from a sample of the target distribution. -By learning the total displacement from a generally, uninformative corrupted sample obtained diffusing information and a sample from an unknown distribution--significant ($`\Vert \epsilon \Vert > 0`$) whenever input and target distribution are sufficiently different-- @hoDenoisingDiffusionProbabilistic2020 show that one can approximate the underlying distribution reversing the displacement, *denoising* samples. Interestingly, under the hypothesis real-world data belongs to a single higher dimensional manifold (Manifold Hypothesis), @permenterInterpretingImprovingDiffusion2024 show that diffusion learns the gradient of a distance function from any off-point manifold (such as perturbed, uniformative samples), and the data manifold itself. Following this gradient--i.e., denoising a sample from an uninformative distribution--corresponds to projecting back into the manifold, yielding a procedure to sample from unknown distributions by means of Euclidean projection. Indeed, under the assumption that $`p_\theta (z_{t-1} \vert z_t)`$ is Gaussian, then sampling $`z_{t-1} \sim p_\theta(\bullet \vert z_{t})`$ corresponds to computing $`\htmlId{diffusion-denoising-definition}{z_{t-1} = \frac{1}{\sqrt{\alpha_t}} \left( z_t - \frac{\beta_t}{\sqrt{1 - \bar\alpha_t}} \epsilon_\theta(z_t, t) \right) + \sigma_t \epsilon, \quad \epsilon \sim \mathcal N(\mathbf{0}, \mathbf{I}),}`$ thus showing that the lower-level latent variables in a DM can be obtained by iteratively removing noise from the one-step higher order variable, using the noise regressor $`\epsilon_\theta(z_t, t)`$ learned minimizing [diffusion-simplified-loss]. +By learning the total displacement from a generally, uninformative corrupted sample obtained diffusing information and a sample from an unknown distribution--significant ($`\Vert \epsilon \Vert > 0`$) whenever input and target distribution are sufficiently different-- @hoDenoisingDiffusionProbabilistic2020 show that one can approximate the underlying distribution reversing the displacement, *denoising* samples. Interestingly, under the hypothesis real-world data belongs to a single higher dimensional manifold (Manifold Hypothesis), @permenterInterpretingImprovingDiffusion2024 show that diffusion learns the gradient of a distance function from any off-point manifold (such as perturbed, uniformative samples), and the data manifold itself. Following this gradient--i.e., denoising a sample from an uninformative distribution--corresponds to projecting back into the manifold, yielding a procedure to sample from unknown distributions by means of Euclidean projection. Indeed, under the assumption that $`p_\theta (z_{t-1} \vert z_t)`$ is Gaussian, then sampling $`z_{t-1} \sim p_\theta(\bullet \vert z_{t})`$ corresponds to computing +``` math +\begin{align} + z_{t-1} = \frac{1}{\sqrt{\alpha_t}} \left( z_t - \frac{\beta_t}{\sqrt{1 - \bar\alpha_t}} \epsilon_\theta(z_t, t) \right) + \sigma_t \epsilon, \quad \epsilon \sim \mathcal N(\mathbf{0}, \mathbf{I}), +\end{align} +``` +thus showing that the lower-level latent variables in a DM can be obtained by iteratively removing noise from the one-step higher order variable, using the noise regressor $`\epsilon_\theta(z_t, t)`$ learned minimizing [diffusion-simplified-loss]. #### Flow Matching \\ -The posterior parametrization adopted by DMs proved traditionally effective, yet it raised concerns circa its efficiency at inference time, where a possibly large of compute-expensive denoising steps are needed in order to recover a sample from the target distribution. Flow Matching (FM) @lipmanFlowMatchingGenerative2023 extends DMs to the general case of arbitrary, parametrized likelihood and posteriors, and in this defines a superseding class of GMs providing a unified framework for learning *continuous transformations* between distributions, encompassing and generalizing DMs. Instead of a *stochastic, discrete, multi-step* denoising process, FM aims to learn a *deterministic, continuous, differentiable flow* $`\psi [0,1] \times Z \mapsto Z`$, formalized starting from possibly time-dependent vector field $`v: [0,1] \times Z \mapsto Z`$ transporting samples from a simple prior distribution $`p_0`$--e.g., a standard Gaussian--to a more complex, potentially unknown data distribution $`p_1`$ over time. Note how FM models time $`t \in [0,1]`$ to be varying continuously while moving away *from* an easy-to-sample distribution $`p_0`$ *towards* the unknown data-distribution, $`p_1`$. This results in a continuous and deterministic trajectory for each sample, which can be more efficient to generate compared to the stochastic paths of DMs. Formally, FM can be fully characterized by an ordinary differential equation (ODE) relating instantaneous variations of flows with the underlying vector field, and hence providing complete trajectories over the distributions’ support when integrating over time, -$$ -`\frac{d}{dt} \psi(z, t) = v(t, \psi(t, z))\\ - \psi(0, z) = z` -$$ - +The posterior parametrization adopted by DMs proved traditionally effective, yet it raised concerns circa its efficiency at inference time, where a possibly large of compute-expensive denoising steps are needed in order to recover a sample from the target distribution. Flow Matching (FM) @lipmanFlowMatchingGenerative2023 extends DMs to the general case of arbitrary, parametrized likelihood and posteriors, and in this defines a superseding class of GMs providing a unified framework for learning *continuous transformations* between distributions, encompassing and generalizing DMs. Instead of a *stochastic, discrete, multi-step* denoising process, FM aims to learn a *deterministic, continuous, differentiable flow* $`\psi [0,1] \times Z \mapsto Z`$, formalized starting from possibly time-dependent vector field $`v: [0,1] \times Z \mapsto Z`$ transporting samples from a simple prior distribution $`p_0`$--e.g., a standard Gaussian--to a more complex, potentially unknown data distribution $`p_1`$ over time. Note how FM models time $`t \in [0,1]`$ to be varying continuously while moving away *from* an easy-to-sample distribution $`p_0`$ *towards* the unknown data-distribution, $`p_1`$. This results in a continuous and deterministic trajectory for each sample, which can be more efficient to generate compared to the stochastic paths of DMs. Formally, FM can be fully characterized by an ordinary differential equation (ODE) relating instantaneous variations of flows with the underlying vector field, and hence providing complete trajectories over the distributions’ support when integrating over time, +``` math +\begin{align} + \frac{d}{dt} \psi(z, t) &= v(t, \psi(t, z)) \\ + \psi(0, z) &= z +\end{align} +``` FM proved very effective in a variety of applications, ranging from image @esserScalingRectifiedFlow2024 and video generation @polyakMovieGenCast2025 to robotics control @blackp0VisionLanguageActionFlow2024. Most notably, in their introductory work on FM for GM, @lipmanFlowMatchingGenerative2023 show how DMs can be seen as a specific instance of FM where the *conditional* target vector field $`u`$ approximated by the noise regressor corresponds to ``` math @@ -741,7 +790,7 @@ FM proved very effective in a variety of applications, ranging from image @esse Note that the traditional discrete-time noise-scheduler $`{\beta_t}_{t=0}^T`$ is now generalized to a continuous map $`\beta : [0,1] \mapsto \mathbb R^+`$. Crucially, @lipmanFlowMatchingGenerative2023 prove that by exclusively optimizing the vector field for individual data points $`z_0 \in \mathcal D`$ individually, one also retrieves the optimal flow to morph the entire support of the initial distribution $`p_0`$ into $`p_1 \ \text{s.t.} \mathcal D \sim p_1`$.
- +

<span id="ch4-normalizing-flows" style="position: absolute;"></span>

Probability distributions can be modified applying vector fields resulting in a flow of mass in the support. When acting over time, vector fields can effectively change the distribution’s structure.
@@ -749,13 +798,20 @@ Note that the traditional discrete-time noise-scheduler $`{\beta_t}_{t=0}^T`$ is While the noising schedule of DMs results in a stochastic process that resembles a random walk, FM allows for more general--potentially, deterministic--likelihood and posterior parametrization. In the FM literature the likelihood and posterior probabilty densities defined along a HMLV model are typically jointly referred to as a *probability path*, where the distributions for successive adjacent transitions in the HMLV model are related by the (normalized) flow between them (Figure [ch4-normalizing-flows]). The inherent flexibility of FM is one of their key advantages over DMs, as it opens up the possibility of *learning* more efficient paths. For instance, one can design probability paths inspired by Optimal Transport (OT)--a subdiscipline studying the problem of finding the most efficient way to morph one probability distribution into another. Probability paths obtained through OT paths tend to be *straighter* than diffusion paths (Figure [ch4-diffusion-paths-versus-fm]), which can lead to faster and more stable training, as well as higher-quality sample generation with fewer steps at inference time. By avoiding unnecessary backtracking associated with the inherent stochastic nature of both the noising and denoising process in DMs, test-time compute is typically significantly reduced, while retaining comparable results @lipmanFlowMatchingGenerative2023.
- +

<span id="ch4-diffusion-paths-versus-fm" style="position- absolute;"></span>

Compared to diffusion, flow matching distorts distribution along a less randomic pattern, resulting in a clearer interpolation between source and target distribution. The visualization shows an example comparison between these two methods on joint distribution of robot observations and actions over T = 50 steps.
-In practice, FM can be applied to generative modeling by learning a vector field regressor $`v_\theta(z, t)`$ to approximate a given target vector field $`u(t, z)`$. In the particular case of DMs, $`u(t, z)`$ is defined as in [fm-diffusion-vector-field], while in priciple the target vector field can be learned to induce a particular transportation, or fixed according to OT. Given a sample from the data distribution $`z_1 \sim p_1`$ and a sample from an easy-to-sample prior $`z_0 \sim p_0`$, CFM defines a simple path between them using *linear interpolation* between samples $`z_t = (1-t)z_0 + t z_1`$, resulting in the target vector field $`u(t, z_t) = z_1 - z_0`$. Then, a FM model can be trained with the simple regression objective defined as $`\htmlId{flow-matching-objective}{\mathcal L(\theta) = \mathbb{E}_{t, z_0, z_1} \big[ - \Vert v_\theta((1-t)z_0 + t z_1, t) - (z_1 - z_0) \Vert^2 \big], \quad t \sim \mathcal{U}([0,1]),}`$ where $`z_0 \sim p_0(\bullet)`$ and $`z_1 \sim p_1(\bullet)`$. Note how in [flow-matching-objective]--differently from [diffusion-simplified-loss]--time is assumed to be varying continuously $`t \sim \mathcal U([0,1])`$ rather than discretely $`t \sim \mathcal U(\{0,1\})`$, a key property of flow-based models. The objective in [flow-matching-objective] directly regresses the learned vector field onto the simple, straight path connecting a point from the prior and a point from the data, providing a simulation-free training procedure that is both stable and efficient. At inference time, samples are generated by starting with $`z_0 \sim p_0`$ and iteratively refined according to $`\frac{dz}{dt} = v_\theta(z_t, t)`$ for $`t \in [0,1]`$--an operation that can be numerically carried out with standard ODE solvers. +In practice, FM can be applied to generative modeling by learning a vector field regressor $`v_\theta(z, t)`$ to approximate a given target vector field $`u(t, z)`$. In the particular case of DMs, $`u(t, z)`$ is defined as in [fm-diffusion-vector-field], while in priciple the target vector field can be learned to induce a particular transportation, or fixed according to OT. Given a sample from the data distribution $`z_1 \sim p_1`$ and a sample from an easy-to-sample prior $`z_0 \sim p_0`$, CFM defines a simple path between them using *linear interpolation* between samples $`z_t = (1-t)z_0 + t z_1`$, resulting in the target vector field $`u(t, z_t) = z_1 - z_0`$. Then, a FM model can be trained with the simple regression objective defined as +``` math +\begin{align} + + \mathcal L(\theta) = \mathbb{E}_{t, z_0, z_1} \big[ + \Vert v_\theta((1-t)z_0 + t z_1, t) - (z_1 - z_0) \Vert^2 \big], \quad t \sim \mathcal{U}([0,1]), +\end{align} +``` +where $`z_0 \sim p_0(\bullet)`$ and $`z_1 \sim p_1(\bullet)`$. Note how in [flow-matching-objective]--differently from [diffusion-simplified-loss]--time is assumed to be varying continuously $`t \sim \mathcal U([0,1])`$ rather than discretely $`t \sim \mathcal U(\{0,1\})`$, a key property of flow-based models. The objective in [flow-matching-objective] directly regresses the learned vector field onto the simple, straight path connecting a point from the prior and a point from the data, providing a simulation-free training procedure that is both stable and efficient. At inference time, samples are generated by starting with $`z_0 \sim p_0`$ and iteratively refined according to $`\frac{dz}{dt} = v_\theta(z_t, t)`$ for $`t \in [0,1]`$--an operation that can be numerically carried out with standard ODE solvers. ### Action Chunking with Transformers @@ -763,15 +819,22 @@ While GMs prove useful in learning complex, high-dimensional multi-modal distrib On the robot learning side of their contributions, @zhaoLearningFineGrainedBimanual2023 adopt transformers as the architectural backbone to learn a *Conditional* VAE @sohnLearningStructuredOutput2015. Conditional VAEs are a variation of the more standard VAE formulation introducing a conditioning variable on sampling from the latent prior, allowing the modeling of *one-to-many* relationships between latent and data samples. Further, in stark contrast with previous work @florenceImplicitBehavioralCloning2022, @jannerPlanningDiffusionFlexible2022, @zhaoLearningFineGrainedBimanual2023 do not learn a full joint $`p_\theta(o,a)`$ on observation and actions. While the *policy* distribution $`p_\theta(a \vert o)`$ can in principle be entirely described from its joint $`p_\theta(o,a)`$, it is often the case that the conditional distribution is intractable when using function approximators, as $`p_\theta(a \vert o) = \tfrac{p_\theta(o,a)}{\int_\mathcal Ap_\theta(o,a)}`$ and the integral in the denominator is typically intractable. Instead of modeling the full joint using a vanilla VAE, @zhaoLearningFineGrainedBimanual2023 propose learning a *conditional* VAE @sohnLearningStructuredOutput2015 modeling the policy distribution directly $`p (a \vert o)`$. -In practice, when learning from demonstrations adopting CVAEs results in a slight modification to the VAE objective in [ELBO], which is adapted to $`\htmlId{c-ELBO}{\text{ELBO}_{\mathcal D}(\theta, \phi, \omega) = \sum_{i=0}^{N} \left( +In practice, when learning from demonstrations adopting CVAEs results in a slight modification to the VAE objective in [ELBO], which is adapted to +``` math +\begin{align} + + \text{ELBO}_{\mathcal D}(\theta, \phi, \omega) = \sum_{i=0}^{N} \left( \mathbb{E}_{z \sim q_\phi(\cdot \vert o_i, a_i)} \big[ \log p_\theta(a_i \vert z, o_i) \big] - \text{D}_{\text{KL}}\big[ q_\phi(z \vert o_i, a_i) \Vert p_\omega(z \vert o_i) \big] - \right)}`$ Notice how in [c-ELBO] we are now also learning a new set of parameters $`\omega`$ for the prior distribution in the latent space. Effectively, this enables conditioning latent-space sampling (and thus reconstruction) during training, and potentially inference, providing useful when learning inherently conditional distributions like policies. Further, ACT is trained as a $`\beta`$-CVAE @higgins2017beta, using a weight of the KL regularization term in [c-ELBO] as an hyperparameter regulating the information condensed in the latent space, where higher $`\beta`$ results in a less expressive latent space. + \right) +\end{align} +``` +Notice how in [c-ELBO] we are now also learning a new set of parameters $`\omega`$ for the prior distribution in the latent space. Effectively, this enables conditioning latent-space sampling (and thus reconstruction) during training, and potentially inference, providing useful when learning inherently conditional distributions like policies. Further, ACT is trained as a $`\beta`$-CVAE @higgins2017beta, using a weight of the KL regularization term in [c-ELBO] as an hyperparameter regulating the information condensed in the latent space, where higher $`\beta`$ results in a less expressive latent space. In their work, @zhaoLearningFineGrainedBimanual2023 ablated using a GM to learn from human demonstrations compared to a simpler, supervised objective, $`\mathcal L_1(a,a^\prime) = \Vert a - a^\prime \Vert_1`$. Interestingly, they found the performance of these two approaches to be comparable when learning from *scripted* demonstrations. That is, when learning from data collected rolling out a predetermined set of commands $`[q^c_0, q^c_1, \dots]`$, GM did *not* prove competitive compared to standard supervised learning. However, when learning from human demonstrations--i.e., from data collected executing commands coming from a human controller $`[q^h_0, q^h_1, \dots]`$--they found performance (success rate on a downstream task) to be severily (-33.3%) hindered from adopting a standard supervised learning objective compared to a richer, potentially more complex to learn variational objective, in keeping with the multimodal nature of human demonstrations data and findings presented in @florenceImplicitBehavioralCloning2022. The authors also ablate the action chunking paradigm, reporting significant performance gains for performing action chunking (1% vs. 44% success rate). To avoid acting openloop, @zhaoLearningFineGrainedBimanual2023 design an inference process consisting in performing inference at every timestep $`t`$ and then aggregate overlapping chunks using chunks’ exponential moving average.
- +

<span id="ch4-act" style="position: absolute;"></span>

Action Chunking with Transformer (ACT), as in @zhaoLearningFineGrainedBimanual2023. ACT introduces an action chunking paradigm to cope with high-dimensional multi-modal demonstration data, and a transformer-based CVAE architecture.
@@ -779,7 +842,7 @@ In their work, @zhaoLearningFineGrainedBimanual2023 ablated using a GM to learn In ACT (Figure [ch4-act]), inference for a given observation $`o \in \mathcal O`$ could be performed by (1) computing a prior $`p_\omega(z \vert o)`$ for the latent and (2) decoding an action chunk from a sampled latent $`z \sim p_\omega(\bullet \vert o)`$, similarily to how standard VAEs generate samples, with the exception that vanilla VAEs typically pose $`p(z\vert o) \equiv p(z) \sim N(\mathbf{0}, \mathbf{I})`$ and thus skip (1).
- +

<span id="ch4-act-encoder" style="position: absolute;"></span>

The CVAE encoder used in ACT. Input action chunks are first embedded and aggregated with positional embeddings, before being processed alongside embedded proprioperceptive information, and a learned [CLS] token used to aggregate input level information, and predict the style variable z. The encoder is entirely disregarded at inference time.
@@ -787,7 +850,7 @@ In ACT (Figure  +

<span id="ch4-act-decoder" style="position: absolute;"></span>

The CVAE decoder used in ACT, comprising of a full encoder-decoder Transformer architecture. Camera observations from all n camera views are first embedded using pre-trained visual encoders, and then concatenated to the corresponding positional embeddings. Then, alongside embeddings for the proprioperceptive information available and the style variable z retrieved from the CVAE encoder, the Transformer encoder shares the matrices K, Q with the Transformer decoder, trained to decode fixed position embeddings into action valid chunks.
@@ -798,18 +861,20 @@ However, the authors claim using a deterministic procedure to derive $`z`$ may b DMs proved very effective in approximating complex highly dimensional distributions, such as distributions over images @hoDenoisingDiffusionProbabilistic2020 or videos @polyakMovieGenCast2025, thanks to their inherent capability to deal with multimodal data and training stability. In Diffusion Policy (DP), @chiDiffusionPolicyVisuomotor2024 present an application of DMs the field of robot learning, leveraging diffusion to model human expert demonstrations in a variety of simulated and real-world tasks. Similarily to Action Chunking with Transformer @zhaoLearningFineGrainedBimanual2023, @chiDiffusionPolicyVisuomotor2024 (1) adopt a modified *observation-conditioned target distribution* instead of the full joint $`p(o,a)`$ and (2) predict multiple actions into the future instead of a single action. Besides the intractability of the observations’ marginal $`p_\theta(o)`$ given $`p_\theta(o,a)`$, DP’s rationale for modeling the data distribution via $`p_\theta(a \vert o)`$ stems from the rather test-time compute intensive nature of diffusion, whereby generating actions *alongside* observations is likely to result in higher complexity and thus a likely larger number of denoising operations, which would prove ultimately pointless considering robotics applications rely on the capability to generate controls rather than reproducing observations. -In practice, conditioning on observation data is achieved conditioning the added noise regressor $`\epsilon_\theta`$ introduced in 
[diffusion-simplified-loss] on a stack of $`T_o`$ observations, resulting in the *conditional* simplified diffusion objective -$$ -`\htmlId{diffusion-policy-objective}{\mathcal L(\theta) = \mathbb{E}_{t, a_{t:t+H_a}, \epsilon} \big[ - \Vert \epsilon - \epsilon_\theta(\sqrt{\bar \alpha_t} a_{t:t+T_a} + \epsilon \sqrt{1 - \bar \alpha_t}, t, o_{t-T_o:t}) \Vert^2 \big],\\ - t \sim \mathcal{U}(\{1,\dots,T\}), \quad +In practice, conditioning on observation data is achieved conditioning the added noise regressor $`\epsilon_\theta`$ introduced in [diffusion-simplified-loss] on a stack of $`T_o`$ observations, resulting in the *conditional* simplified diffusion objective +``` math +\begin{align} + \mathcal L(\theta) &= \mathbb{E}_{t, a_{t:t+H_a}, \epsilon} \big[ + \Vert \epsilon - \epsilon_\theta(\sqrt{\bar \alpha_t} a_{t:t+T_a} + \epsilon \sqrt{1 - \bar \alpha_t}, t, o_{t-T_o:t}) \Vert^2 \big], \\ + & t \sim \mathcal{U}(\{1,\dots,T\}), \quad a_{t:t+T_a}, o_{t-T_o:t} \sim \mathcal{D}, \quad - \epsilon \sim \mathcal{N}(\mathbf{0},\mathbf{I}). \notag}` -$$ - Notice how in [diffusion-policy-objective] the noise regressor is conditioned both on the latent variable rank $`t`$ *and* on a stack of previous observations $`o_{t-T_o-t}`$.  @chiDiffusionPolicyVisuomotor2024 claim the combination of (1) conditioning on a horizon of previous observations and (2) predicting multiple actions into the future allows DP to *commit to specific modes* in the data at inference time, which proves essential for good performance and avoiding undecisiveness. + \epsilon \sim \mathcal{N}(\mathbf{0},\mathbf{I}). \notag +\end{align} +``` +Notice how in [diffusion-policy-objective] the noise regressor is conditioned both on the latent variable rank $`t`$ *and* on a stack of previous observations $`o_{t-T_o-t}`$.  @chiDiffusionPolicyVisuomotor2024 claim the combination of (1) conditioning on a horizon of previous observations and (2) predicting multiple actions into the future allows DP to *commit to specific modes* in the data at inference time, which proves essential for good performance and avoiding undecisiveness.
- +

<span id="diffusion-policy-architecture" style="position: absolute;"></span>

The Diffusion Policy archicture, as in @chiDiffusionPolicyVisuomotor2024. A stack of Ho previous observations is used as external conditioning to denoise a group of Ha actions. Conditioning is used at every layer of a U-Net block, and in practice allows to obtain fully-formed action chunks with as little as T = 10 denoising steps.
@@ -834,7 +899,7 @@ We directly assess the lack of adaptiveness of robot systems due to acting open-
- +

<span id="ch4-async-inference" style="position: absolute;"></span>

Asynchronous inference. Illustration of the asynchronous inference stack. Note that the policy can be run on a remote server, possibly with GPUs.
@@ -874,7 +939,7 @@ Interestingly, the behavior of async inference can be studied analytically. Firs
- +

<span id="ch4-queues" style="position: absolute;"></span>

Action queue size evolution at runtime for various levels of g when (A) not filtering out observation based on joint-space similarity and (B) filtering out near-duplicates observation, measuring their similarity in joint-space.
@@ -905,7 +970,7 @@ TL;DR Openly available large scale datasets and the development of stable, expre The advent of large models trained on internet-scale datasets has drastically influenced fields like Computer Vision (CV) and Natural Language Processing (NLP), shifting the paradigm towards combining (1) an initial, task-agnostic large-scale pre-training stage and a (2) task-specific, adjustment phase. The pre-training/adaptation paradigm has now largely replaced more classic approaches consisting of task-specific data collection, curation and model training in many subdomains within CV and NLP, motivated by the main drawback of limited scalability for *task-specific approaches*, traditionally labor intensive. Factors including (1) the advancements in generalist models learned with self-supervision for perception @oquabDINOv2LearningRobust2024 or semantic understanding @devlinBERTPretrainingDeep2019 and (2) the popularization collective efforts to aggregate large-scale openly available datasets @collaborationOpenXEmbodimentRobotic2025, @khazatskyDROIDLargeScaleInTheWild2025 are increasingly pushing the field of robot learning towards the pre-train-and-adapt paradigm. This shift taps into the long-standing challenge of developing generalist robot policies, and holds the premise to surpass traditionally siloed approaches to robotics problems and develop a *foundation robotics model*. While Section [learning-bc-single] introduced methods for learning *single-task policies* such as ACT or Diffusion Policy, in this section we present advancements in developing *generalist, multi-task, policies*, capable of performing a wide range of tasks across different environments and embodiments, and guided by unstructured instructions given via natural language.
- +

<span id="ch5-ml-vs-robotics-foundation" style="position: absolute;"></span>

Fields within ML such as Computer Vision and NLP converged on the development of foundation models, trained on a variety of large scale models and capable to perform multiple downstream tasks (top). Conversely, robotics suffered from limited standardization in terms of the architectures used, and siloed, task specific datasets, incurring in a high degree of fragmentation which traditionally hindered the development of generalist models for robotics in favour of task-specific models (bottom).
@@ -915,7 +980,7 @@ The advent of large models trained on internet-scale datasets has drastically in The remarkable success of foundation models in NLP and CV is predicated on two core principles: architectural innovation and joint data-compute scaling. The transformer architecture proved instrumental in capturing long-range dependencies in sequential data such as text, and its stability and expressivity made it the *de facto* standard for modern large-scale models trained on internet-scale amounts of data. In stark contrast with popular NLP @raffelExploringLimitsTransfer2023 and CV @ImageNet_VSS09 general-purpose datasets, the field of robotics has historically developed around task-specific datasets which hinders scalability across problems, resulting in a concrete data deficit for general-purpose robot learning. Unlike the wealth of relatively readily available text and images on the internet, robotics data is intrinsically embodied--datasets collected for a manipulation robot typically differ entirely from locomotion datasets. Further, datasets consisting of expert demonstrations are (1) intrinsically expensive to collect (2) and notoriously heterogeneous--different human experts may perform the same task optimally yet in very different ways. In particular, since each expert trajectory is tied to a specific robot platform and the operating conditions of its environment and task, data heterogeneity has long posed a *methodological* challenge for scaling robotics datasets via aggregation. Beyond this, heterogeneity also raises *conceptual* issues: naively mixing data across embodiments can induce negative transfer, as control strategies developed in isolation for different robot systems in different environments may even conflict when combined. Thus, the high degree of fragmentation of robotics datasets and tasks has traditionally led to the development of *specialist* policies, trained on small, task-specific datasets, and which excel at their designated task but fail to generalize to new situations (Figure [ch5-ml-vs-robotics-foundation]).
- +

<span id="ch5-generalist-policies-timeline" style="position: absolute;"></span>

Early efforts in the development of generalist models for robotics include BC-Zero @jangBCZZeroShotTask2022, RT-1 @brohanRT1RoboticsTransformer2023, and RT-2 @brohanRT2VisionLanguageActionModels2023: large scale models trained on thousands of demonstrations. The open release of the Open-X @collaborationOpenXEmbodimentRobotic2025 and DROID datasets @khazatskyDROIDLargeScaleInTheWild2025 fostered the development of open source models: OpenVLA @kimOpenVLAOpenSourceVisionLanguageAction2024, π0 @blackp0VisionLanguageActionFlow2024 and SmolVLA @shukorSmolVLAVisionLanguageActionModel2025.
@@ -929,7 +994,7 @@ Traditionally, research involved not only training the model but also collecting The success of large, proprietary models like RT-1 and RT-2, highlighted a growing accessibility gap in robotics research, as training and deploying large-scale models requires computational resources simply unattainable for most research institutions. The OpenVLA project @kimOpenVLAOpenSourceVisionLanguageAction2024 emerged in direct contrast of closed-source counterparts, as a community-driven effort to create powerful, openly available VLAs. In particular, @kimOpenVLAOpenSourceVisionLanguageAction2024 trained OpenVLA by exclusively leveraging openly available data (970K+ from the Open-X dataset), and share training recipes alongside the model weights. Architecturally, OpenVLA integrates a pre-trained vision encoder to project visual tokens into the embedding space of Llama2-7B @touvronLlama2Open2023 language model backbone. The language model backbone is then used to predict *discrete action tokens* over 256 activation levels.
- +

<span id="ch5-trends" style="position: absolute;"></span>

Robot learning is undergoing a paradigmatic shift: centralized data collections (A, left) are increasingly larger, often comprising Ms of demonstrations, and (A, right) decentralized approaches to data collection are also rising as an alternative for large scale data collection. (B) Generalist models are also becoming increasingly smaller and easier to run on limited hardware.
@@ -953,7 +1018,7 @@ Recently, compute efficiency has also become a central focus in VLM research. Se $`\pi_0`$ @blackp0VisionLanguageActionFlow2024 introduce a VLA consisting of a MoE architecture consisting of (1) a pre-trained VLM backbone (Gemma 2.6B @teamGemma2Improving2024) and (2) a dedicated action expert used to generate continuous actions via flow matching. Images and language are embedded with a late-fusion VLM (PaliGemma), while proprioceptive state and actions chunks are routed to a smaller action expert, initialized from scratch. The two separate experts communicate via self-attention layers, but maintain disjoint weights to obtain query, key and values matrices at each layer, maintaining specialization while efficiently allocating computation.
- +

<span id="ch5-pi0" style="position: absolute;"></span>

The π0architecture, as in @blackp0VisionLanguageActionFlow2024. Vision and language tokens are routed to a VLM backbone which is prevented from attending robot proprioperceptive states and action tokens, which are instead routed to a smaller subset of weights within the architecture. The architecture is trained with Flow Matching on 10M+ trajectories from a mixture of closed and openly available datasets.
@@ -967,20 +1032,22 @@ Concretely, $`\pi_0`$ is a unified transformer with two disjoint sets of weights }, \quad \mathbf{1}: \text{Bidirectional Attention}, \ \mathbf{0}: \text{Masked Attention}`$ Note how *intra*-block directional attention allows tokens to communicate freely, while *inter*-block communication is mediated by the attention mask $`\mathbf{A}`$. *Blockwise causal masking* effectively prevents the pre-trained perception-language tokens from attending to robotics-tokens, likely out of distribution for VLM backbones traditionally trained on large corpora of internet, non-robotics, data. Crucially, because communication is obstructed between image-language tokens, proprioperceptive and action tokens, one can cache keys and values across denoising steps at runtime time, incuring in a reduced computational footprint and faster inference. -In $`\pi_0`$, both the VLM backbone and action expert are update using a *flow matching* loss, and in particular are updated minimizing: -$$ -`\htmlId{pi0-loss}{\mathcal{L}(\phi, \theta) = +In $`\pi_0`$, both the VLM backbone and action expert are update using a *flow matching* loss, and in particular are updated minimizing: +``` math +\begin{align} + \mathcal{L}(\phi, \theta) &= \mathbb{E}_{\tau, \epsilon, o_t, a_{t:t+H_a}}\Big[ \big\Vert v_\theta(\underbrace{\tau a_{t:t+H_a} + (1-\tau) \epsilon}_{\tilde a_{t:t+H_a}},\, o_t,\, \tau) - (\epsilon - a_{t:t+H_a}) \big\Vert^2 - \Big],\\ - \tau \sim \mathrm{Beta}_{[0,s]}(1.5,1), \quad + \Big], \\ + &\tau \sim \mathrm{Beta}_{[0,s]}(1.5,1), \quad \epsilon \sim \mathcal{N}(\mathbf{0}, \mathbf{I}), \quad - o_t, a_{t:t+H_a} \sim \mathcal D \notag}` -$$ - Where the experts parametrized by the separate weights $`\phi, \theta`$ interact with each other via self-attention layers only, so that the action expert $`v_\theta`$ internal computations also depend on the VLM backbone’s parameters $`\phi`$. Importantly, @blackp0VisionLanguageActionFlow2024 minimize [pi0-loss] over both the multimodal backbone and action expert parameters, thus updating the internal representations of the VLM using BC-specific gradients. In contrast, @driessKnowledgeInsulatingVisionLanguageAction2025 later show that failing to insulate the VLM knowledge from the flow matching gradients actually harms performance. Inference is performed iteratively refining action chunks while numerically forward-integrating the vector field predicted by the action expert, + o_t, a_{t:t+H_a} \sim \mathcal D \notag +\end{align} +``` +Where the experts parametrized by the separate weights $`\phi, \theta`$ interact with each other via self-attention layers only, so that the action expert $`v_\theta`$ internal computations also depend on the VLM backbone’s parameters $`\phi`$. Importantly, @blackp0VisionLanguageActionFlow2024 minimize [pi0-loss] over both the multimodal backbone and action expert parameters, thus updating the internal representations of the VLM using BC-specific gradients. In contrast, @driessKnowledgeInsulatingVisionLanguageAction2025 later show that failing to insulate the VLM knowledge from the flow matching gradients actually harms performance. Inference is performed iteratively refining action chunks while numerically forward-integrating the vector field predicted by the action expert, ``` math \begin{equation} a_{t:t+H_a}^{\tau + \delta} = a_{t:t+H_a}^{\tau } + \delta v_\theta(a_{t:t+H_a}^{\tau }, o_t) @@ -991,7 +1058,7 @@ Flow matching  can be seen as a continuous time, detetrministic generalization
-r0.4 image +r0.4 image \\ @@ -1010,7 +1077,7 @@ Lastly, @blackp0VisionLanguageActionFlow2024 present cross-embodiment experimen VLAs remain in an early stage of development and are not yet as mature or widely adopted as LLMs and VLMs. Further, much of the impactful VLA progress remains proprietary, with many models sharing only weights while withholding full training details and essential methodological components. SmolVLA @shukorSmolVLAVisionLanguageActionModel2025 is an entirely open-source research effort, aiming to democratize the developments of robotics foundation models by open sourcing model, training recipes and data used.
- +

<span id="ch5-smolvla" style="position: absolute;"></span>

The SmolVLA architecture, as in @shukorSmolVLAVisionLanguageActionModel2025. SmolVLA is a compact MoE model trained with flow matching to denoise action chunks. Vision and language tokens are fed to a VLM backbone, and share information with the proprioperceptive and action tokens via the attention mechanism. The attention expert interleaves SA and CA layers for further conditioning on the visual features from the VLM backbone. SmolVLA skips computations and reduces the visual tokens, resulting in 6x less memory usage than π0.
diff --git a/app/scripts/latex-to-markdown/output/main.mdx b/app/scripts/latex-to-mdx/output/main.mdx similarity index 91% rename from app/scripts/latex-to-markdown/output/main.mdx rename to app/scripts/latex-to-mdx/output/main.mdx index 4eb0c151f674be4e9c1b5e21bc569f85e9da7f86..9ad4c1e32ebca86fca36c154b9262b785a19a0ae 100644 --- a/app/scripts/latex-to-markdown/output/main.mdx +++ b/app/scripts/latex-to-mdx/output/main.mdx @@ -89,7 +89,7 @@ We sincerely hope this tutorial serves as a valuable starting point for your jou layout="fixed" alt="Figure" /> -

<span id="figure1" style="position: absolute;"></span>

+
lerobot is the open-source library for end-to-end robotics developed by Hugging Face. The library is vertically integrated on the entire robotics stack, supporting low-level control of real-world robot devices, advanced data and inference optimizations, as well as SOTA robot learning methods with simple implementations in pure Pytorch.
@@ -181,7 +181,12 @@ streaming_dataset = StreamingLeRobotDataset( # Get the 100th frame in the dataset by sample = dataset[100] print(sample) -# +# { +# 'observation.state': tensor([...]), +# 'action': tensor([...]), +# 'observation.images.wrist_camera': tensor([3, C, H, W]), for delta timesteps +# ... +# } batch_size=16 # wrap the dataset in a DataLoader to use process it batches for training purposes @@ -233,7 +238,7 @@ TL;DR Learning-based approaches to robotics are motivated by the need to (1) gen layout="fixed" alt="Figure" /> -

<span id="generating-motion-atlas" style="position: absolute;"></span>

+
Overview of methods to generate motion (clearly non-exhausitve, see @bekrisStateRobotMotion2024). The different methods can be grouped based on whether they explicitly (dynamics-based) or implicitly (learning-based) model robot-environment interactions.
@@ -251,7 +256,7 @@ Methods to produce robotics motion range from traditional *explicit* models-- -

<span id="robotics-platforms-atlas" style="position: absolute;"></span>

+
Different kinds of motions are achieved with potentially very different robotic platforms. From left to right, top to bottom: ViperX, SO-100, Boston Dynamics’ Spot, Open-Duck, 1X’s NEO, Boston Dynamics’ Atlas. This is an example list of robotic platforms and is (very) far from being exhaustive.
@@ -275,7 +280,7 @@ Recently, the development of low-cost manipulators like the ALOHA @zhaoLearning layout="fixed" alt="Figure" /> -

<span id="robotic-platforms-costs" style="position: absolute;"></span>

+
Cheaper, more accessible robots are starting to rival traditional platforms like the Panda arm platforms in adoption in resource-constrained scenarios. The SO-100, in particular, has a cost in the 100s of Euros, and can be entirely 3D-printed in hours, while the industrially-manufactured Panda arm costs tens of thousands of Euros and is not openly available.
@@ -289,7 +294,7 @@ Deriving an intuition as per why learning-based approaches are gaining popularit layout="fixed" alt="Figure" /> -

<span id="make-so100-planar-manipulator" style="position: absolute;"></span>

+
The SO-100 arm is a 6-dof manipulator arm. Preventing some of its joints (shoulder pane, wrist flex and wrist roll) from actuating, it can be represented as a traditional 2-dof planar manipulator (the gripper joint in the end-effector is not considered towards the count of the degrees of freedom used to produce motion).
@@ -308,7 +313,7 @@ All these simplifying assumptions leave us with the planar manipulator of Figure layout="fixed" alt="Figure" /> -

<span id="planar-manipulation-simple" style="position: absolute;"></span>

+
Free to move
@@ -319,7 +324,7 @@ All these simplifying assumptions leave us with the planar manipulator of Figure layout="fixed" alt="Figure" /> -

<span id="planar-manipulator-floor" style="position: absolute;"></span>

+
Constrained by the surface
@@ -330,25 +335,24 @@ All these simplifying assumptions leave us with the planar manipulator of Figure layout="fixed" alt="Figure" /> -

<span id="planar-manipulator-floor-shelf" style="position: absolute;"></span>

+
Constrained by surface and (fixed) obstacle
Planar, 2-dof schematic representation of the SO-100 manipulator under diverse deployment settings. From left to right: completely free of moving; constrained by the presence of the surface; constrained by the surface and presence of obstacles. Circular arrows around each joint indicate the maximal rotation feasible at that joint.
-Considering the (toy) example presented in Figure [planar-manipulation-simple], then we can analytically write the end-effector’s position $p \in \mathbb R^2$ as a function of the robot’s configuration, $p = p(q), p: \mathcal Q \mapsto \mathbb R^2$. In particular, we have: - -$$ -`p(q) = \begin{pmatrix} p_x(\theta_1, \theta_2)\\ p_y(\theta_1, \theta_2) \end{pmatrix} = \begin{pmatrix} l \cos(\theta_1) + l \cos(\theta_1 + \theta_2)\\ l \sin(\theta_1) + l \sin(\theta_1 + \theta_2) \end{pmatrix} \in S^{n=2}_{l_1+l_2} = \{ p(q) \in \mathbb R^2: \Vert p(q) \Vert_2^2 \leq (2l)^2, \ \forall q \in \mathcal Q \}` -$$ - - +Considering the (toy) example presented in Figure [planar-manipulation-simple], then we can analytically write the end-effector’s position $p \in \mathbb R^2$ as a function of the robot’s configuration, $p = p(q), p: \mathcal Q \mapsto \mathbb R^2$. In particular, we have: $p(q) = \begin{pmatrix} p_x(\theta_1, \theta_2) \\ p_y(\theta_1, \theta_2) \end{pmatrix} = \begin{pmatrix} l \cos(\theta_1) + l \cos(\theta_1 + \theta_2) \\ l \sin(\theta_1) + l \sin(\theta_1 + \theta_2) \end{pmatrix} \in S^{n=2}_{l_1+l_2} = \{ p(q) \in \mathbb R^2: \Vert p(q) \Vert_2^2 \leq (2l)^2, \ \forall q \in \mathcal Q \}$ Deriving the end-effector’s *pose*--position *and* orientation--in some $m$-dimensional space $\boldsymbol{p} \in \mathcal{P} \subset \mathbb{R}^{m}$ starting from the configuration ${\textnormal{q}}\in \mathcal Q \subset \mathbb R^n$ of a $n$-joints robot is referred to as *forward kinematics* (FK), whereas identifying the configuration corresponding to any given target pose is termed *inverse kinematics* (IK). In that, FK is used to map a robot configuration into the corresponding end-effector pose, whereas IK is used to reconstruct the configuration(s) given an end-effector pose. In the simplified case here considered (for which $\boldsymbol{p} \equiv p$, as the orientation of the end-effector is disregarded for simplicity), one can solve the problem of controlling the end-effector’s location to reach a goal position $p^*$ by solving analytically for $q: p(q) = f_{\text{FK}}(q) = p^*$. However, in the general case, one might not be able to solve this problem analytically, and can typically resort to iterative optimization methods comparing candidate solutions using a loss function (in the simplest case, $\Vert p(q) - p^* \Vert_2^2$ is a natural candidate), yielding: -$\htmlId{ik_problem}{\min_{q \in \mathcal Q} \Vert p(q) - p^* \Vert_2^2 \, .}$ +``` math +\begin{align} +\min_{q \in \mathcal Q} \Vert p(q) - p^* \Vert_2^2 \, . + +\end{align} +``` Exact analytical solutions to IK are even less appealing when one considers the presence of obstacles in the robot’s workspace, resulting in constraints on the possible values of $q \in \mathcal Q \subseteq [-\pi, +\pi]^n \subset \mathbb R^n$ in the general case of $n$-links robots. @@ -356,7 +360,13 @@ For instance, the robot in Figure [ik_problem] for a feasible $q$--only proves useful in determining information regarding the robot’s configuration in the goal pose, and crucially does not provide information on the *trajectory* to follow over time to reach a target pose. Expert-defined trajectories obviate to this problem providing a length-$K$ succession of goal poses $\tau_K = [p^*_0, p^*_1, \dots p^*_K]$ for tracking. In practice, trajectories can also be obtained automatically through *motion planning* algorithms, thus avoiding expensive trajectory definition from human experts. However, tracking $\tau_K$ via IK can prove prohibitively expensive, as tracking would require $K$ resolutions of eq. [ik_problem] (one for each target pose). *Differential* inverse kinematics (diff-IK) complements IK via closed-form solution of a variant of eq. [ik_problem]. Let $J(q)$ denote the Jacobian matrix of (partial) derivatives of the FK-function $f_\text{FK}- \mathcal Q \mapsto \mathcal P$, such that $J(q) = \frac{\partial f_{FK}(q)}{\partial q }$. Then, one can apply the chain rule to any $p(q) = f_{\text{FK}}(q)$, deriving $\dot p = J(q) \dot q$, and thus finally relating variations in the robot configurations to variations in pose, thereby providing a platform for control. -Given a desired end-effector trajectory $\dot {p}^*(t)$ (1) indicating anchor regions in space and (2) how much time to spend in each region, diff-IK finds $\dot q(t)$ solving for joints’ *velocities* instead of *configurations*, $\htmlId{reg_ik_velocity}{\dot q(t) = \arg\min_\nu \; \lVert J(q(t)) \nu - \dot {p}^*(t) \rVert_2^2}$ +Given a desired end-effector trajectory $\dot {p}^*(t)$ (1) indicating anchor regions in space and (2) how much time to spend in each region, diff-IK finds $\dot q(t)$ solving for joints’ *velocities* instead of *configurations*, +``` math +\begin{align} +\dot q(t) = \arg\min_\nu \; \lVert J(q(t)) \nu - \dot {p}^*(t) \rVert_2^2 + +\end{align} +``` Unlike eq. [ik_problem], solving for $\dot q$ is much less dependent on the environment (typically, variations in velocity are constrained by physical limits on the actuators). Conveniently, eq. [reg_ik_velocity] also often admits the closed-form solution $\dot q = J(q)^+ \dot {p}^*$, where $J^+(q)$ denotes the Moore-Penrose pseudo-inverse of $J(q)$. Finally, discrete-time joint configurations $q$ can be reconstructed from joint velocities $\dot q$ using forward-integration on the continuous-time joint velocity , $q_{t+1} = q_t + \Delta t\,\dot q_t$ for a given $\Delta t$, resulting in tracking via diff-IK. @@ -401,7 +411,7 @@ Despite the last 60+ years of robotics research, autonomous robots are still lar layout="fixed" alt="Figure" /> -

<span id="classical-limitations" style="position: absolute;"></span>

+
Dynamics-based approaches to robotics suffer from several limitations: (1) orchestrating multiple components poses integration challenges; (2) the need to develop custom processing pipelines for the sensing modalities and tasks considered hinders scalability; (3) simplified analytical models of physical phenomena (here friction at the gripper; credits to @antonovaReinforcementLearningPivoting2017) limit real-world performance. Lastly, (4) dynamics-based methods overlook trends in the availability and growth of robotics data.
@@ -438,7 +448,7 @@ TL;DR The need for expensive high-fidelity simulators can be obviated by learnin layout="fixed" alt="Figure" /> -

<span id="robot-learning-upsides" style="position: absolute;"></span>

+
Learning-based robotics streamlines perception-to-action by learning a (1) unified high-level controller capable to take (2) high-dimensional, unstructured sensorimotor information. Learning (3) does not require a dynamics model and instead focuses on interaction data, and (4) empirically correlates with the scale of the data used.
@@ -454,7 +464,7 @@ Being a field at its relative nascent stages, no prevalent technique(s) proved d layout="fixed" alt="Figure" /> -

<span id="robot-learning-atlas" style="position: absolute;"></span>

+
Overview of the robot learning methods implemented in lerobot.
@@ -470,7 +480,7 @@ Figure  -

<span id="robotics-with-rl-examples" style="position: absolute;"></span>

+
Examples of two different robotics tasks performed using RL. In the manipulation task (A) an agent learns to reach for a yellow plastic block in its environment, and to put it inside of a box. In the locomotion task (B) an agent learns to move its center of mass sideways without falling.
@@ -488,7 +498,7 @@ The RL framework @suttonReinforcementLearningIntroduction2018, which we briefly layout="fixed" alt="Figure" /> -

<span id="rl-most-famous-pic" style="position: absolute;"></span>

+
Agent-Environment interaction diagram (image credits to @suttonReinforcementLearningIntroduction2018).
@@ -510,30 +520,35 @@ A length-$T$ *trajectory* is the (random) sequence ``` math \htmlId{trajectory_definition}{\tau = (s_0, a_0, r_0, s_1, a_1, r_1, \dots, s_{T-1}, a_{T-1}, r_{T-1}, s_T),} ``` -with per-step rewards defined as $r_t = r (s_t, a_t, s_{t+1})$ for ease of notation.Interestingly, assuming both the environment dynamics and conditional distribution over actions given states--the *policy*--to be *Markovian*: -$$ -`\htmlId{dynamics_markovian}{\mathbb P(s_{t+1}\vert s_t, a_t, s_{t-1}, a_{t-1}, \dots s_0, a_0 ) = \mathbb P (s_{t+1}\vert s_t, a_t)\\ \mathbb P(a_t\vert s_t, a_{t-1}, s_{t-1}, s_0, a_0) = \mathbb P(a_t\vert s_t)}` -$$ +with per-step rewards defined as $r_t = r (s_t, a_t, s_{t+1})$ for ease of notation.Interestingly, assuming both the environment dynamics and conditional distribution over actions given states--the *policy*--to be *Markovian*: - The probability of observing a given trajectory $\tau$ factorizes into +``` math +\begin{align} +\mathbb P(s_{t+1}\vert s_t, a_t, s_{t-1}, a_{t-1}, \dots s_0, a_0 ) &= \mathbb P (s_{t+1}\vert s_t, a_t) \\ +\mathbb P(a_t\vert s_t, a_{t-1}, s_{t-1}, s_0, a_0) &= \mathbb P(a_t\vert s_t) +\end{align} +``` +The probability of observing a given trajectory $\tau$ factorizes into ``` math \htmlId{traj_prob}{\mathbb P(\tau) = \mathbb P (s_0) \prod_{t=0}^{T-1} \mathbb P (s_{t+1}\vert s_t, a_t)\ \mathbb P(a_t\vert s_t).} ``` Policies $\mathbb P(a_t\vert s_t)$ are typically indicated as $\pi(a_t\vert s_t)$, and often parametrized via $\theta$, yielding $\pi_\theta (a_t\vert s_t)$. Policies are trained optimizing the (discounted) *return* associated to a given $\tau$, i.e. the (random) sum of measured rewards over trajectory: + ``` math G(\tau) = \sum_{t=0}^{T-1} \gamma^{t} r_t. ``` -In that, agents seek to learn control strategies (*policies*, $\pi_\theta$) maximizing the expected return $\mathbb E_{\tau \sim \pi_\theta} G(\tau)$. For a given dynamics $\mathcal D$--i.e., for a given problem--taking the expectation over the (possibly random) trajectories resulting from acting according to a certain policy provides a direct, goal-conditioned ordering in the space of all the possible policies $\Pi$, yielding the (maximization) target $J : \Pi \mapsto \mathbb R$ - -$$ -`\htmlId{RL-j-function}{J(\pi_\theta) = \mathbb E_{\tau \sim \mathbb P_{\theta; \mathcal D}} [G(\tau)],\\ \mathbb P_{\theta; \mathcal D} (\tau) = \rho \prod_{t=0}^{T-1} \mathcal D (s_t, a_t, s_{t+1})\ \pi_\theta (a_t\vert s_t).}` -$$ - - +In that, agents seek to learn control strategies (*policies*, $\pi_\theta$) maximizing the expected return $\mathbb E_{\tau \sim \pi_\theta} G(\tau)$. For a given dynamics $\mathcal D$--i.e., for a given problem--taking the expectation over the (possibly random) trajectories resulting from acting according to a certain policy provides a direct, goal-conditioned ordering in the space of all the possible policies $\Pi$, yielding the (maximization) target $J : \Pi \mapsto \mathbb R$ +``` math +\begin{align} + J(\pi_\theta) &= \mathbb E_{\tau \sim \mathbb P_{\theta; \mathcal D}} [G(\tau)], \\ + \mathbb P_{\theta; \mathcal D} (\tau) &= \rho \prod_{t=0}^{T-1} \mathcal D (s_t, a_t, s_{t+1})\ \pi_\theta (a_t\vert s_t). +\end{align} +``` Because in the RL framework the agent is assumed to only be able to observe the environment dynamics and not to intervene on them, 
[RL-j-function] varies exclusively with the policy followed. In turn, MDPs naturally provide a framework to optimize over the space of the possible behaviors an agent might enact ($\pi \in \Pi$), searching for the *optimal policy* $\pi^* = \arg \max_{\theta} J(\pi_\theta)$, where $\theta$ is the parametrization adopted by the policy set $\Pi: \pi_\theta \in \Pi, \ \forall \theta$. Other than providing a target for policy search, $G(\tau)$ can also be used as a target to discriminate between states and state-action pairs. Given any state $s \in \mathcal S$--e.g., a given configuration of the robot--the *state-value* function + ``` math V_\pi(s) = \mathbb E_{\tau \sim \pi} [G(\tau) \big \vert s_0 = s] ``` @@ -541,13 +556,17 @@ can be used to discriminate between desirable and undesirable state in terms of ``` math Q_\pi(s,a) = \mathbb E_{\tau \sim \pi} [G (\tau) \big \vert s_0 = s, a_0=a] ``` -Crucially, value functions are interrelated: -$$ -`\htmlId{q-as-v}{Q_\pi(s_t, a_t) = \mathbb{E}_{s_{t+1}\sim \mathbb P(\bullet \vert s_t, a_t)} [r_t + \gamma V_\pi(s_{t+1})]\\ V_\pi(s_t) = \mathbb E_{a_t\sim \pi(\bullet \vert s_t)} [Q_\pi (s_t, a_t)]}` -$$ +Crucially, value functions are interrelated: - Inducing an ordering over states and state-action pairs under $\pi$, value functions are central to most RL algorithms. A variety of methods have been developed in RL as standalone attemps to find (approximate) solutions to the problem of maximizing cumulative reward (Figure [rl-algos-atlas]). +``` math +\begin{align} +Q_\pi(s_t, a_t) &= \mathbb{E}_{s_{t+1}\sim \mathbb P(\bullet \vert s_t, a_t)} [r_t + \gamma V_\pi(s_{t+1})] \\ +V_\pi(s_t) &= \mathbb E_{a_t\sim \pi(\bullet \vert s_t)} [Q_\pi (s_t, a_t)] + +\end{align} +``` +Inducing an ordering over states and state-action pairs under $\pi$, value functions are central to most RL algorithms. A variety of methods have been developed in RL as standalone attemps to find (approximate) solutions to the problem of maximizing cumulative reward (Figure [rl-algos-atlas]).
-

<span id="rl-algos-atlas" style="position: absolute;"></span>

+
Popular RL algorithms. See @SpinningUp2018 for a complete list of citations.
@@ -579,7 +598,7 @@ Second, learning with a limited number of samples remains problematic in RL, -

<span id="synthetic-vs-real-duck" style="position: absolute;"></span>

+
Simulated (left) vs. real-world (right) OpenDuck. Discrepancies in the simulation dynamics (reality gap) pose risks to policy transfer.
@@ -593,7 +612,7 @@ Training RL policies in simulation @tobinDomainRandomizationTransferring2017 ad layout="fixed" alt="Figure" /> -

<span id="ducks-on-terrains" style="position: absolute;"></span>

+
The same locomotion task can be carried out in different (simulated) domains (exemplified by the difference in terrains) at training time, resulting to increased robustness over diverse environment dynamics.
@@ -628,25 +647,39 @@ Q_{i+1}(s_t, a_t) \leftarrow \mathbb E_{s_{t+1} \sim \mathbb P(\bullet \vert s_t ``` Then, one can derive the (ideally, near-optimal) policy by explicitly maximizing over the action space the final (ideally, near-optimal) estimate $Q_K \approx Q^*$ at each timestep. In fact, under certain assumptions on the MDP considered, $Q_K \to Q^* \, \text{as } K \to \infty$. -Effective in its early applications to small-scale discrete problems and theoretically sound, vanilla Q-learning was found complicated to scale to large $\mathcal S\times \mathcal A$ problems, in which the storing of $Q : \mathcal S\times \mathcal A\mapsto \mathbb R$ alone might result prohibitive. Also, vanilla Q-learning is not directly usable for *continuous*, unstructured state-action space MPDs, such as those considered in robotics. In their seminal work on *Deep Q-Learning* (DQN), @mnihPlayingAtariDeep2013 propose learning Q-values using deep convolutional neural networks, thereby accomodating for large and even unstructured *state* spaces. DQN parametrizes the Q-function using a neural network with parameters $\theta$, updating the parameters by sequentially minimizing the expected squared temporal-difference error (TD-error, $\delta_i$): - -$$ -`\htmlId{dqn-loss}{\mathcal L(\theta_i) = \mathbb E_{(s_t, a_t) \sim \chi(\bullet)} \big[ (\underbrace{y_i - Q_{\theta_i}(s_t, a_t)}_{\delta_i})^2 \big],\\ y_i = \mathbb E_{s_{t+1} \sim \mathbb P(\bullet \vert s_t, a_t)} \big[ r_t + \gamma \max_{a_t\in \mathcal A} Q_{\theta_{i-1}} (s_{t+1}, a_{t+1}) \big],}` -$$ - - Where $\chi$ represents a behavior distribution over state-action pairs. Crucially, $\chi$ can in principle be different from the policy being followed, effectively allowing to reuse prior data stored in a *replay buffer* in the form of $(s_t, a_t, r_t, s_{t+1})$ transitions, used to form the TD-target $y_i$, TD-error $\delta_i$ and loss function [dqn-loss] via Monte-Carlo (MC) estimates. +Effective in its early applications to small-scale discrete problems and theoretically sound, vanilla Q-learning was found complicated to scale to large $\mathcal S\times \mathcal A$ problems, in which the storing of $Q : \mathcal S\times \mathcal A\mapsto \mathbb R$ alone might result prohibitive. Also, vanilla Q-learning is not directly usable for *continuous*, unstructured state-action space MPDs, such as those considered in robotics. In their seminal work on *Deep Q-Learning* (DQN), @mnihPlayingAtariDeep2013 propose learning Q-values using deep convolutional neural networks, thereby accomodating for large and even unstructured *state* spaces. DQN parametrizes the Q-function using a neural network with parameters $\theta$, updating the parameters by sequentially minimizing the expected squared temporal-difference error (TD-error, $\delta_i$): +``` math +\begin{align} +\mathcal L(\theta_i) &= \mathbb E_{(s_t, a_t) \sim \chi(\bullet)} + \big[ + (\underbrace{y_i - Q_{\theta_i}(s_t, a_t)}_{\delta_i})^2 + \big], \\ + y_i &= \mathbb E_{s_{t+1} \sim \mathbb P(\bullet \vert s_t, a_t)} \big[ r_t + \gamma \max_{a_t\in \mathcal A} Q_{\theta_{i-1}} (s_{t+1}, a_{t+1}) \big], +\end{align} +``` +Where $\chi$ represents a behavior distribution over state-action pairs. Crucially, $\chi$ can in principle be different from the policy being followed, effectively allowing to reuse prior data stored in a *replay buffer* in the form of $(s_t, a_t, r_t, s_{t+1})$ transitions, used to form the TD-target $y_i$, TD-error $\delta_i$ and loss function [dqn-loss] via Monte-Carlo (MC) estimates. While effective in handling large, unstructured state spaces for discrete action-space problems, DQN application’s to continous control problems proved challenging. Indeed, in the case of high-capacity function approximators such as neural networks, solving $\max_{a_t \in \mathcal A} Q_\theta(s_t, a_t)$ at each timestep is simply unfeasible due to the (1) continous nature of the action space ($\mathcal A\subset \mathbb R^n$ for some $n$) and (2) impossibility to express the find a cheap (ideally, closed-form) solution to $Q_\theta$.  @silverDeterministicPolicyGradient2014 tackle this fundamental challenge by using a *deterministic* function of the state $s_t$ as policy, $\mu_\phi(s_t) = a_t$, parametrized by $\phi$. Thus, policies can be iteratively refined updating $\phi$ along the direction: ``` math \htmlId{deterministic-pg}{d_\phi = \mathbb E_{s_t \sim \mathbb P (\bullet)} [\nabla_\phi Q(s_t, a_t)\vert_{a_t = \mu_\phi(s_t)}] = \mathbb E_{s_t \sim \mathbb P(\bullet)} [\nabla_{a_t} Q(s_t, a_t) \vert_{a_t = \mu_\phi(s_t)} \cdot \nabla_\phi \mu(s_t)]} ``` + Provably, [deterministic-pg] is the *deterministic policy gradient* (DPG) of the policy $\mu_\phi$ @silverDeterministicPolicyGradient2014, so that updates $\phi_{k+1}\leftarrow \phi_k + \alpha d_\phi$ are guaranteed to increase the (deterministic) cumulative discounted reward, $J(\mu_\phi)$.  @lillicrapContinuousControlDeep2019 extended DPG to the case of (1) high-dimensional unstructured observations and (2) continuous action spaces, introducing Deep Deterministic Policy Gradient (DDPG), an important algorithm RL and its applications to robotics. DDPG adopts a modified TD-target compared to the one defined in [TD-target], by maintaining a policy network used to select actions, yielding + ``` math \htmlId{TD-target-ddpg}{y_i = \mathbb E_{s_{t+1} \sim \mathbb P(\bullet \vert s_t, a_t)} \big[ r_t + \gamma Q_{\theta_{i-1}} (s_{t+1}, \mu_\phi(s_{t+1})) \big] .} ``` Similarily to DQN, DDPG also employs the same replay buffer mechanism, to reuse past transitions over training for increased sample efficiency and estimate the loss function via MC-estimates. -Soft Actor-Critic (SAC) @haarnojaSoftActorCriticOffPolicy2018 is a derivation of DDPG in the max-entropy (MaxEnt) RL framework, in which RL agents are tasked with maximizing the discounted cumulative reward, while acting as randomly as possible. MaxEnt RL @haarnojaReinforcementLearningDeep2017 has proven particularly robust thanks to the development of diverse behaviors, incentivized by its entropy-regularization formulation. In that, MaxEnt revisits the RL objective $J (\pi)$ to specifically account for the policy entropy, $\htmlId{J-soft}{J(\pi) = \sum_{t=0}^T \mathbb{E}_{(s_t, a_t) \sim \chi} [r_t + \alpha \mathcal H(\pi (\bullet \vert s_t))]}$ This modified objective results in the *soft* TD-target: +Soft Actor-Critic (SAC) @haarnojaSoftActorCriticOffPolicy2018 is a derivation of DDPG in the max-entropy (MaxEnt) RL framework, in which RL agents are tasked with maximizing the discounted cumulative reward, while acting as randomly as possible. MaxEnt RL @haarnojaReinforcementLearningDeep2017 has proven particularly robust thanks to the development of diverse behaviors, incentivized by its entropy-regularization formulation. In that, MaxEnt revisits the RL objective $J (\pi)$ to specifically account for the policy entropy, +``` math +\begin{align} + J(\pi) &= \sum_{t=0}^T \mathbb{E}_{(s_t, a_t) \sim \chi} [r_t + \alpha \mathcal H(\pi (\bullet \vert s_t))] +\end{align} +``` + +This modified objective results in the *soft* TD-target: + ``` math \htmlId{soft-td-target}{y_i = \mathbb E_{s_{t+1} \sim \mathbb P( \bullet \vert s_t, a_t)} [r_t + \gamma \left( Q_{\theta_{i-1}} (s_{t+1}, a_{t+1}) - \alpha \log \pi_\phi(a_{t+1} \vert s_{t+1}) \right)], \quad a_{t+1} \sim \pi_\phi(\bullet \vert s_t)} ``` @@ -676,7 +709,7 @@ Lastly, in order to improve on the robustness of their approach to different goa layout="fixed" alt="Figure" /> -

<span id="hil-serl-blocks" style="position: absolute;"></span>

+
(A) HIL-SERL allows for real-world training of high performance RL agents by building on top advancements presented by of SAC, RLPD and SERL. (B) Example of human intervention during a HIL-SERL training process on a SO-100.
@@ -721,7 +754,7 @@ TL;DR Behavioral Cloning provides a natural platform to learn from real-world in layout="fixed" alt="Figure" /> -

<span id="ch4-bc-trajectories" style="position: absolute;"></span>

+
(A) Average (with standard deviation) evolution of the actuation levels over the first 5 recorded episodes in lerobot/svla_so101_pickplace. Proprioperceptive state provide invaluable to determine the robot’s state during an episode. (B) Camera frames are also recorded alongside measurements on the robot’s state, capturing information about the robot’s interaction with its environment.
@@ -737,7 +770,7 @@ Formally, let $\mathcal D = \{ \tau^{(i)} \}_{i=1}^N$ be a set of expert traject layout="fixed" alt="Figure" /> -

<span id="ch4-observation-action-mapping" style="position: absolute;"></span>

+
Sample observations and action pairs over the course of a given trajectory recorded in lerobot/svla_so101_pickplace. Observations, comprising of both proprioperceptive and visual information, are recorded alongside the configuration of a second, leader robot controlled by a human expert, providing complete information for regressing actions given observation.
@@ -759,7 +792,7 @@ Despite the inherent challenges of learning on non-i.i.d. data, the BC formulati layout="fixed" alt="Figure" /> -

<span id="ch4-issues-with-bc" style="position: absolute;"></span>

+
Point-wise policies suffer from limitations due to (A) covariate shifts and poor approximation of (B) multimodal demonstrations. (A) Initially small errors may drive the policy out of distribution, incuring in a vicious circle ultimately resulting in failure. (B) Both modes of reaching for a target object in a scene, either left or right-first, are equally as good and thus equally as likely to be present in a dataset of human demonstrations, ultimately resulting in multimodal demonstrations.
@@ -779,7 +812,7 @@ Generative Models (GMs) aim to learn the stochastic process underlying the very layout="fixed" alt="Figure" /> -

<span id="ch4-task-effect-on-pairs" style="position: absolute;"></span>

+
Intuitively, latent variable in a single latent model may contain information regarding the task being performed, which directly results in the likelihood of the same observation-action pair being different for two different tasks. When (A) picking a block the likelihood of a wide gripper’s opening should be higher than narrower one, while it should be the opposite when (B) pushing the block.
@@ -797,41 +830,64 @@ Intuitively, in the case of observation-action pairs $(o, a)$ for a robotics app layout="fixed" alt="Figure" /> -

<span id="ch4-latent-variable-model" style="position: absolute;"></span>

+
(A) The latent variable model in a robotics application regulates influence between observed ( o, a) variables and an unobservable latent variable. (B) VAEs approximate exact latent variable models by means of variational inference.
-Given a dataset $\mathcal D$ consisting of $N$ i.i.d. observation-action pairs, the log-likelihood of all datapoints under $\theta$ (in Bayesian terms, the *evidence* $p_\theta(\mathcal D)$) can thus be written as: - -$$ -`\htmlId{evidence-definition-1}{\log p_\theta(\mathcal D) = \log \sum_{i=0}^N p_\theta ((o,a)_i)\\ = \log \sum_{i=0}^N \int_{\text{supp}({Z})} p_\theta((o,a)_i \vert z) p(z)\\ = \log \sum_{i=0}^N \int_{\text{supp}({Z})} \frac{q_\theta(z \vert (o,a)_i)}{q_\theta(z \vert (o,a)_i)} \cdot p_\theta((o,a)_i \vert z) p(z)\\ = \log \sum_{i=0}^N \mathbb E_{z \sim p_\theta(\bullet \vert (o,a)_i)} [\frac{p(z)}{q_\theta(z \vert (o,a)_i)} \cdot p_\theta((o,a)_i \vert z)],}` -$$ - - where we used [BC-latent-variable] in [evidence-definition-1], multiplied by $1 = \frac{q_\theta(z \vert (o,a)_i)}{q_\theta(z \vert (o,a)_i)}$ in [evidence-definition-2], and used the definition of expected value in [evidence-definition]. +Given a dataset $\mathcal D$ consisting of $N$ i.i.d. observation-action pairs, the log-likelihood of all datapoints under $\theta$ (in Bayesian terms, the *evidence* $p_\theta(\mathcal D)$) can thus be written as: +``` math +\begin{align} + \log p_\theta(\mathcal D) &= \log \sum_{i=0}^N p_\theta ((o,a)_i) \\ + &= \log \sum_{i=0}^N \int_{\text{supp}({Z})} p_\theta((o,a)_i \vert z) p(z) \\ + &= \log \sum_{i=0}^N \int_{\text{supp}({Z})} \frac{q_\theta(z \vert (o,a)_i)}{q_\theta(z \vert (o,a)_i)} \cdot p_\theta((o,a)_i \vert z) p(z) \\ + &= \log \sum_{i=0}^N \mathbb E_{z \sim p_\theta(\bullet \vert (o,a)_i)} [\frac{p(z)}{q_\theta(z \vert (o,a)_i)} \cdot p_\theta((o,a)_i \vert z)], +\end{align} +``` +where we used [BC-latent-variable] in [evidence-definition-1], multiplied by $1 = \frac{q_\theta(z \vert (o,a)_i)}{q_\theta(z \vert (o,a)_i)}$ in [evidence-definition-2], and used the definition of expected value in [evidence-definition]. In the special case where one assumes distributions to be tractable, $p_\theta (\mathcal D)$ is typically tractable too, and $\max_\theta \log p_\theta(\mathcal D)$ provides a natural target for (point-wise) infering the unknown parameters $\theta$ of the generative model. Unfortunately, [evidence-definition] is rarely tractable when the distribution $p$ is modeled with approximators such as neural networks, especially for high-dimensional, unstructured data. In their seminal work on Variational Auto-Encoders (VAEs), @kingmaAutoEncodingVariationalBayes2022 present two major contributions to learn complex latent-variable GMs on unstructured data, proposing (1) a tractable, variational lower-bound to [evidence-definition] as an optimization target to jointly learn likelihood and posterior and (2) high-capacity function approximators to model the likelihood $p_\theta(o,a\vert z)$ and (approximate) posterior distribution $q_\phi(z \vert o,a) \approx q_\theta(z \vert o,a)$. -In particular, the lower bound on [evidence-definition] (Evidence LOwer Bound, *ELBO*) can be derived from [evidence-definition] applying Jensen’s inequality--$\log \mathbb{E}[\bullet] \geq \mathbb{E} [\log (\bullet)]$--yielding: +In particular, the lower bound on [evidence-definition] (Evidence LOwer Bound, *ELBO*) can be derived from [evidence-definition] applying Jensen’s inequality--$\log \mathbb{E}[\bullet] \geq \mathbb{E} [\log (\bullet)]$--yielding: +``` math +\begin{align} + \log p_\theta(\mathcal D) &\geq \sum_{i=0}^{N} \left( + \mathbb{E}_{z \sim p_\theta(\cdot \vert (o,a)_i)} \big[ \log p_\theta((o,a)_i \vert z) \big] + + \mathbb{E}_{z \sim p_\theta(\cdot \vert (o,a)_i)} [\log \left( \frac{p(z)}{q_\theta(z \vert (o,a)_i)} \right)] + \right) \\ + &= \sum_{i=0}^{N} \left( + \mathbb{E}_{z \sim p_\theta(\cdot \vert (o,a)_i)} \big[ \log p_\theta((o,a)_i \vert z) \big] + - \text{D}_{\text{KL}}\big[ q_\theta(z \vert (o,a)_i) \Vert p(z) \big] + \right) +\end{align} +``` -$$ -`\htmlId{ELBO-intractable}{\log p_\theta(\mathcal D) \geq \sum_{i=0}^{N} \left( \mathbb{E}_{z \sim p_\theta(\cdot \vert (o,a)_i)} \big[ \log p_\theta((o,a)_i \vert z) \big] + \mathbb{E}_{z \sim p_\theta(\cdot \vert (o,a)_i)} [\log \left( \frac{p(z)}{q_\theta(z \vert (o,a)_i)} \right)] \right)\\ = \sum_{i=0}^{N} \left( \mathbb{E}_{z \sim p_\theta(\cdot \vert (o,a)_i)} \big[ \log p_\theta((o,a)_i \vert z) \big] - \text{D}_{\text{KL}}\big[ q_\theta(z \vert (o,a)_i) \Vert p(z) \big] \right)}` -$$ +The true, generally intractable posterior $p_\theta (z \vert o,a)$ prevents computing both the expectation and KL divergence terms in [ELBO-intractable], and therefore @kingmaAutoEncodingVariationalBayes2022 propose deriving the ELBO using an *approximate* posterior $q_\phi(z \vert o,a)$, resulting in the final, tractable ELBO objective, - The true, generally intractable posterior $p_\theta (z \vert o,a)$ prevents computing both the expectation and KL divergence terms in [ELBO-intractable], and therefore @kingmaAutoEncodingVariationalBayes2022 propose deriving the ELBO using an *approximate* posterior $q_\phi(z \vert o,a)$, resulting in the final, tractable ELBO objective, $\htmlId{ELBO}{\text{ELBO}_{\mathcal D}(\theta, \phi) = \sum_{i=0}^{N} \left( \mathbb{E}_{z \sim q_\phi(\cdot \vert (o,a)_i)} \big[ \log p_\theta((o,a)_i \vert z) \big] - \text{D}_{\text{KL}}\big[ q_\phi(z \vert (o,a)_i) \Vert p(z) \big] \right)}$ From Jensen’s inequality, maximizing ELBO results in maximizing the log-likelihood of the data too, thus providing a natural, tractable optimization target. Indeed, expectations can be estimated using MC estimates from the learned distributions in [ELBO], while the KL-divergence term can typically be computed in closed-form (1) modeling $q_\phi$ as a Gaussian $q_\phi(z \vert o,a) = \mathcal N\big(\mu_\phi(o,a), \Sigma_\phi(o,a) \big)$ and (2) imposing a standard Gaussian prior on the latent space, $p(z) = \mathcal N(\mathbf{0}, \mathbf{I})$. +``` math +\begin{align} +\text{ELBO}_{\mathcal D}(\theta, \phi) = \sum_{i=0}^{N} \left( + \mathbb{E}_{z \sim q_\phi(\cdot \vert (o,a)_i)} \big[ \log p_\theta((o,a)_i \vert z) \big] + - \text{D}_{\text{KL}}\big[ q_\phi(z \vert (o,a)_i) \Vert p(z) \big] + \right) + +\end{align} +``` +From Jensen’s inequality, maximizing ELBO results in maximizing the log-likelihood of the data too, thus providing a natural, tractable optimization target. Indeed, expectations can be estimated using MC estimates from the learned distributions in [ELBO], while the KL-divergence term can typically be computed in closed-form (1) modeling $q_\phi$ as a Gaussian $q_\phi(z \vert o,a) = \mathcal N\big(\mu_\phi(o,a), \Sigma_\phi(o,a) \big)$ and (2) imposing a standard Gaussian prior on the latent space, $p(z) = \mathcal N(\mathbf{0}, \mathbf{I})$. An intuitive explanation of the learning dynamics of VAEs can be given considering the equivalent case of *minimizing the negative ELBO*, which admits a particularly interpretable factorization - - -$$ -`\htmlId{VAE-min-neg-ELBO}{\min_{\theta, \phi} - \text{ELBO}_{\mathcal (o,a) \sim \mathcal D}(\theta, \phi) = \min_{\theta, \phi}\mathbf{L^{\text{rec}}}(\theta) + \mathbf{L^{\text{reg}}}(\phi)\\ \mathbf{L^{\text{rec}}}(\theta) = \mathbb{E}_{z \sim q_\phi(\cdot \vert o,a} \big[ \log p_\theta(o,a \vert z) \big]\\ \mathbf{L^{\text{reg}}}(\phi) = \text{D}_{\text{KL}}\big[ q_\phi(z \vert o,a) \Vert p(z) \big]}` -$$ - - +``` math +\begin{align} +\min_{\theta, \phi} - \text{ELBO}_{\mathcal (o,a) \sim \mathcal D}(\theta, \phi) &= \min_{\theta, \phi}\mathbf{L^{\text{rec}}}(\theta) + \mathbf{L^{\text{reg}}}(\phi) \\ +\mathbf{L^{\text{rec}}}(\theta) &= \mathbb{E}_{z \sim q_\phi(\cdot \vert o,a} \big[ \log p_\theta(o,a \vert z) \big] \\ +\mathbf{L^{\text{reg}}}(\phi) &= \text{D}_{\text{KL}}\big[ q_\phi(z \vert o,a) \Vert p(z) \big] +\end{align} +``` For any given $(o,a)$ pair, the expected value term of [VAE-Lrec] is typically computed via MC estimates, resulting in + ``` math -\mathbb{E}_{z \sim q_\phi(\bullet \vert o,a)} \big[ \log p_\theta(o,a \vert z) \big] = \mathbf{L^{\text{rec}}} \approx - \frac{1}{n} \sum_{i=0}^n \log p_\theta(o,a \vert z_i). ``` @@ -843,13 +899,14 @@ Indeed, it is very common in practice to approximate from the learned likelihood #### Diffusion Models -VAEs approximate probability distributions via a *single* latent variable model, assuming the underlying unknown distribution can be factored according to [BC-latent-variable], and solve the variational inference problem of jointly learning the likelihood $p_\theta$ and (approximate) posterior $q_\phi$ for such model. In that, the unknown data distribution $p(o,a)$ is effectively approximated via $\int_Z p(z) p_\theta(o,a \vert z)$, and the underlying generative process reproduced by (1) sampling a latent variable and (2) learning to decode it into a (ideally) high-likelihood sample under the (unknown) $p(o,a)$. Diffusion Models (DMs) @hoDenoisingDiffusionProbabilistic2020 are another class of GMs which treat the similar problem of approximating an underlying unknown data distribution--*variational inference*--by *partially* extending VAEs to the case where *multiple* latent variables influence each other and the generative process underlying $o,a$ itself. In particular, DMs posit the generative process can be decomposed to a series of piece-wise (Markovian) interactions between (latent) variables (Figure [ch4-many-latents]), resulting in - -$$ -`\htmlId{BC-multi-latent-model-1}{p(\underbrace{o,a}_{= z_0}) = \int_{\text{supp}({Z_0})} \int_{\text{supp}({Z_1})} \ldots \int_{\text{supp}({Z_T})} p(z_0, z_1, \dots z_T)\\ p(z_0, z_1, \dots z_T) = p(z_T) \prod_{t=0}^{T} p(z_{t-1} \vert z_t),}` -$$ - - where we explicitly showed the marginalization over the multiple latents in [BC-multi-latent-model-1], and used the law of conditional probability and Markov property in [BC-multi-latent-model-2]. +VAEs approximate probability distributions via a *single* latent variable model, assuming the underlying unknown distribution can be factored according to [BC-latent-variable], and solve the variational inference problem of jointly learning the likelihood $p_\theta$ and (approximate) posterior $q_\phi$ for such model. In that, the unknown data distribution $p(o,a)$ is effectively approximated via $\int_Z p(z) p_\theta(o,a \vert z)$, and the underlying generative process reproduced by (1) sampling a latent variable and (2) learning to decode it into a (ideally) high-likelihood sample under the (unknown) $p(o,a)$. Diffusion Models (DMs) @hoDenoisingDiffusionProbabilistic2020 are another class of GMs which treat the similar problem of approximating an underlying unknown data distribution--*variational inference*--by *partially* extending VAEs to the case where *multiple* latent variables influence each other and the generative process underlying $o,a$ itself. In particular, DMs posit the generative process can be decomposed to a series of piece-wise (Markovian) interactions between (latent) variables (Figure [ch4-many-latents]), resulting in +``` math +\begin{align} + p(\underbrace{o,a}_{= z_0}) &= \int_{\text{supp}({Z_0})} \int_{\text{supp}({Z_1})} \ldots \int_{\text{supp}({Z_T})} p(z_0, z_1, \dots z_T) \\ + p(z_0, z_1, \dots z_T) &= p(z_T) \prod_{t=0}^{T} p(z_{t-1} \vert z_t), +\end{align} +``` +where we explicitly showed the marginalization over the multiple latents in [BC-multi-latent-model-1], and used the law of conditional probability and Markov property in [BC-multi-latent-model-2].
-

<span id="ch4-many-latents" style="position: absolute;"></span>

+
HMLV models posit the data generation process is influenced by a stack of Markov-dependent latent variables, with samples from the posterior distribution being progressively higher up in the hierarchy.
@@ -867,13 +924,16 @@ Similarily to VAEs, providing an exact interpretation for the latent variables i Just like VAEs, DMs attemp to learn to reproduce an underlying data distribution $p (o,a)$ given a collection of i.i.d. samples approximating the model posited to have generated the data in the first place ( [BC-multi-latent-model-1]). Similarily to VAEs, DMs approximate the process of sampling from the unknown $p(o,a)$ (1) sampling from an easy-to-sample distribution (e.g., Gaussian) and (2) learning to reconstruct high-likelihood samples under the unknown distribution. However, in stark contrast with VAEs, the easy-to-sample distribution contains *no mutual information* regarding the data distribution $p(o,a)$. Crucially, as no information from the sample $(o,a)$ (denoted as $z_0 \equiv (o,a)$ for the sake of notation) is assumed to be propagated throughout the chain of latents, the posterior $q(z_t \vert z_{t-1})$ assumes a relatively amicable structure in DMs, reducing complexity. The *true* likelihood $p(z_{t-1} \vert z_t)$ is instead typically approximated using the parametrization $p_\theta (z_{t-1} \vert z_t)$. In that, the information contained in the unknwon data distribution is *reconstructed* via a process in which samples from a fixed distribution are turned into (ideally) high-likelihood samples under $p(o,a)$--a process referred to as *denoising*. -Under such model, we can express the log-likelihood of an arbitrary sample as[^4] - -$$ -`\htmlId{diffusion-likelihood}{\log p_\theta (\underbrace{o,a}_{= z_0}) = \mathbb{E}_{z_1 \sim q(\bullet \vert z_0)} \log p_\theta (z_0 \vert z_1) -\\ \mathbb{E}_{z_{T-1} \sim q(\bullet \vert z_0)} \big[ \text{D}_{\text{KL}}(q(z_T \vert z_{T-1}) \Vert p(z_T) ) \big] - \notag\\ \sum_{t=1}^{T-1} \mathbb{E}_{(z_{t-1}, z_{t+1}) \sim q(\bullet \vert z_0)} \big[ \text{D}_{\text{KL}}(q(z_t \vert z_{t-1}) \Vert p_\theta(z_t \vert z_{t-1}) ) \big], \notag}` -$$ - - providing an optimization target in the form of $\max_\theta \log p_\theta (\mathcal D)$. +Under such model, we can express the log-likelihood of an arbitrary sample as[^4] +``` math +\begin{align} + \log p_\theta (\underbrace{o,a}_{= z_0}) = + &\mathbb{E}_{z_1 \sim q(\bullet \vert z_0)} \log p_\theta (z_0 \vert z_1) - \\ + &\mathbb{E}_{z_{T-1} \sim q(\bullet \vert z_0)} \big[ \text{D}_{\text{KL}}(q(z_T \vert z_{T-1}) \Vert p(z_T) ) \big] - \notag \\ + &\sum_{t=1}^{T-1} \mathbb{E}_{(z_{t-1}, z_{t+1}) \sim q(\bullet \vert z_0)} \big[ \text{D}_{\text{KL}}(q(z_t \vert z_{t-1}) \Vert p_\theta(z_t \vert z_{t-1}) ) \big], \notag +\end{align} +``` +providing an optimization target in the form of $\max_\theta \log p_\theta (\mathcal D)$. In their seminal work on using DMs for variational inference, @hoDenoisingDiffusionProbabilistic2020 introduce major contributions regarding solving $\min_\theta -\log p_\theta(o,a)$. In particular, @hoDenoisingDiffusionProbabilistic2020 exclusively adopt a fixed *Gaussian* posterior in the form of $q(z_t \vert z_{t-1}) = \mathcal{N}(\sqrt{1-\beta_t}z_{t-1}, \beta_t \mathbf I)$. The choice of adopting Gaussians has profound implications on the generative process modeled. Indeed, under the (mild) assumption that the variance is sufficiently small $\beta_t \leq \eta, \eta \in \mathbb R^+$, @sohl-dicksteinDeepUnsupervisedLearning2015 proved that the likelihood $p(z_{t-1} \vert z_t)$ is Gaussian as well, which allows for the particularly convenient parametrization of the approximate likelihood $p_\theta (x_{t-1} \vert x_t) = \mathcal N(\mu_\theta(x_t, t), \Sigma_\theta(x_t,t)), \ t \in [1,T]$, as well as for closed-form tractability of the KL-divergence terms in [diffusion-likelihood]. Further, the posterior’s structure also enables an analytical description for the distribution of the $t$-th latent variable, $q(z_t \vert z_0) = \mathcal N (\sqrt{\bar{\alpha}_t}z_0, (1-\bar{\alpha}_t) \mathbf{I})$, with $\alpha_t = 1-\beta_t, \ \bar \alpha_t = \prod_{k=1}^t \alpha_k$, which conveniently prevents iterative posterior sampling. @@ -885,7 +945,7 @@ In their seminal work on using DMs for variational inference, @hoDenoisingDiffu layout="fixed" alt="Figure" /> -

<span id="diffusion-robot-actions" style="position: absolute;"></span>

+
DMs iteratively corrupt samples (left) from an unknown distribution into a quasi-standard Gaussian (center), learning the displacement field (right) that permits to reconstruct samples from the unknown target distribution by iteratively denoising samples of a tractable, easy-to-sample distribution.
@@ -899,29 +959,45 @@ Finally, adopting Gaussian posteriors permits a particularly pleasing interpreta layout="fixed" alt="Figure" /> -

<span id="ch4-action-vs-observation-distribution" style="position: absolute;"></span>

+
A joint action-observation distribution, in the simplified case where the observation is the elbow-flex actuation in a SO-100, and the action is the recorded position for the same joint in the teleoperator arm. The motion recorded being teleoperated, the points distribute along a the diagonal.
-Because the recorded behavior is teleoperated, measurements mostly distribute along the line $a = o + \eta, \eta \sim N(0,1)$, with $\eta$-variability accouting for minor control inconsistencies (Figure [ch4-action-vs-observation-distribution]). Using Gaussian posteriors--i.e., adding Gaussian noise--effectively simulates a *Brownian motion* for the elements in the distribution’s support (in Figure [diffusion-robot-actions], $\mathcal O\times \mathcal A$), whereby information *diffuses away* from the samples, and comparing the diffused samples to the original data points one can derive an estimate of the total displacement induced by diffusion. Under the only assumption that the likelihood of the diffused samples is low under the original unknown data distribution, then one can effectively approximate the unkwown distribution by learning to *reverse* such displacement. This key intuition allows to write a simplified training objective: $\htmlId{diffusion-simplified-loss}{\mathcal L(\theta) = \mathbb{E}_{t, z_0, \epsilon} \big[ \Vert \epsilon - \epsilon_\theta(\sqrt{\bar \alpha_t} z_0 + \epsilon \sqrt{1 - \bar \alpha_t}, t) \Vert^2 \big], \quad t \sim \mathcal{U}(\{1,\dots,T\}), \quad z_0 \sim \mathcal{D}, \quad \epsilon \sim \mathcal{N}(\mathbf{0},\mathbf{I}).}$ +Because the recorded behavior is teleoperated, measurements mostly distribute along the line $a = o + \eta, \eta \sim N(0,1)$, with $\eta$-variability accouting for minor control inconsistencies (Figure [ch4-action-vs-observation-distribution]). Using Gaussian posteriors--i.e., adding Gaussian noise--effectively simulates a *Brownian motion* for the elements in the distribution’s support (in Figure [diffusion-robot-actions], $\mathcal O\times \mathcal A$), whereby information *diffuses away* from the samples, and comparing the diffused samples to the original data points one can derive an estimate of the total displacement induced by diffusion. Under the only assumption that the likelihood of the diffused samples is low under the original unknown data distribution, then one can effectively approximate the unkwown distribution by learning to *reverse* such displacement. This key intuition allows to write a simplified training objective: +``` math +\begin{align} + + \mathcal L(\theta) = \mathbb{E}_{t, z_0, \epsilon} \big[ + \Vert \epsilon - \epsilon_\theta(\sqrt{\bar \alpha_t} z_0 + \epsilon \sqrt{1 - \bar \alpha_t}, t) \Vert^2 \big], \quad t \sim \mathcal{U}(\{1,\dots,T\}), \quad + z_0 \sim \mathcal{D}, \quad + \epsilon \sim \mathcal{N}(\mathbf{0},\mathbf{I}). +\end{align} +``` In this simplified (minimization) objective, the optimization process differs from [diffusion-likelihood] in that, rather than maxizing $p_\theta$ directly, the parameters $\theta$ of the pairwise likelihood $p_\theta(z_{t-1} \vert z_t)$ are adjusted to *predict the total displacement* $\epsilon$ for a randomly long ($t \sim \mathcal{U}(\{1,\dots,T\}$ )) diffusion process starting from a sample of the target distribution. -By learning the total displacement from a generally, uninformative corrupted sample obtained diffusing information and a sample from an unknown distribution--significant ($\Vert \epsilon \Vert > 0$) whenever input and target distribution are sufficiently different-- @hoDenoisingDiffusionProbabilistic2020 show that one can approximate the underlying distribution reversing the displacement, *denoising* samples. Interestingly, under the hypothesis real-world data belongs to a single higher dimensional manifold (Manifold Hypothesis), @permenterInterpretingImprovingDiffusion2024 show that diffusion learns the gradient of a distance function from any off-point manifold (such as perturbed, uniformative samples), and the data manifold itself. Following this gradient--i.e., denoising a sample from an uninformative distribution--corresponds to projecting back into the manifold, yielding a procedure to sample from unknown distributions by means of Euclidean projection. Indeed, under the assumption that $p_\theta (z_{t-1} \vert z_t)$ is Gaussian, then sampling $z_{t-1} \sim p_\theta(\bullet \vert z_{t})$ corresponds to computing $\htmlId{diffusion-denoising-definition}{z_{t-1} = \frac{1}{\sqrt{\alpha_t}} \left( z_t - \frac{\beta_t}{\sqrt{1 - \bar\alpha_t}} \epsilon_\theta(z_t, t) \right) + \sigma_t \epsilon, \quad \epsilon \sim \mathcal N(\mathbf{0}, \mathbf{I}),}$ thus showing that the lower-level latent variables in a DM can be obtained by iteratively removing noise from the one-step higher order variable, using the noise regressor $\epsilon_\theta(z_t, t)$ learned minimizing [diffusion-simplified-loss]. +By learning the total displacement from a generally, uninformative corrupted sample obtained diffusing information and a sample from an unknown distribution--significant ($\Vert \epsilon \Vert > 0$) whenever input and target distribution are sufficiently different-- @hoDenoisingDiffusionProbabilistic2020 show that one can approximate the underlying distribution reversing the displacement, *denoising* samples. Interestingly, under the hypothesis real-world data belongs to a single higher dimensional manifold (Manifold Hypothesis), @permenterInterpretingImprovingDiffusion2024 show that diffusion learns the gradient of a distance function from any off-point manifold (such as perturbed, uniformative samples), and the data manifold itself. Following this gradient--i.e., denoising a sample from an uninformative distribution--corresponds to projecting back into the manifold, yielding a procedure to sample from unknown distributions by means of Euclidean projection. Indeed, under the assumption that $p_\theta (z_{t-1} \vert z_t)$ is Gaussian, then sampling $z_{t-1} \sim p_\theta(\bullet \vert z_{t})$ corresponds to computing +``` math +\begin{align} + z_{t-1} = \frac{1}{\sqrt{\alpha_t}} \left( z_t - \frac{\beta_t}{\sqrt{1 - \bar\alpha_t}} \epsilon_\theta(z_t, t) \right) + \sigma_t \epsilon, \quad \epsilon \sim \mathcal N(\mathbf{0}, \mathbf{I}), +\end{align} +``` +thus showing that the lower-level latent variables in a DM can be obtained by iteratively removing noise from the one-step higher order variable, using the noise regressor $\epsilon_\theta(z_t, t)$ learned minimizing [diffusion-simplified-loss]. #### Flow Matching -The posterior parametrization adopted by DMs proved traditionally effective, yet it raised concerns circa its efficiency at inference time, where a possibly large of compute-expensive denoising steps are needed in order to recover a sample from the target distribution. Flow Matching (FM) @lipmanFlowMatchingGenerative2023 extends DMs to the general case of arbitrary, parametrized likelihood and posteriors, and in this defines a superseding class of GMs providing a unified framework for learning *continuous transformations* between distributions, encompassing and generalizing DMs. Instead of a *stochastic, discrete, multi-step* denoising process, FM aims to learn a *deterministic, continuous, differentiable flow* $\psi [0,1] \times Z \mapsto Z$, formalized starting from possibly time-dependent vector field $v: [0,1] \times Z \mapsto Z$ transporting samples from a simple prior distribution $p_0$--e.g., a standard Gaussian--to a more complex, potentially unknown data distribution $p_1$ over time. Note how FM models time $t \in [0,1]$ to be varying continuously while moving away *from* an easy-to-sample distribution $p_0$ *towards* the unknown data-distribution, $p_1$. This results in a continuous and deterministic trajectory for each sample, which can be more efficient to generate compared to the stochastic paths of DMs. Formally, FM can be fully characterized by an ordinary differential equation (ODE) relating instantaneous variations of flows with the underlying vector field, and hence providing complete trajectories over the distributions’ support when integrating over time, - -$$ -`\frac{d}{dt} \psi(z, t) = v(t, \psi(t, z))\\ \psi(0, z) = z` -$$ - - +The posterior parametrization adopted by DMs proved traditionally effective, yet it raised concerns circa its efficiency at inference time, where a possibly large of compute-expensive denoising steps are needed in order to recover a sample from the target distribution. Flow Matching (FM) @lipmanFlowMatchingGenerative2023 extends DMs to the general case of arbitrary, parametrized likelihood and posteriors, and in this defines a superseding class of GMs providing a unified framework for learning *continuous transformations* between distributions, encompassing and generalizing DMs. Instead of a *stochastic, discrete, multi-step* denoising process, FM aims to learn a *deterministic, continuous, differentiable flow* $\psi [0,1] \times Z \mapsto Z$, formalized starting from possibly time-dependent vector field $v: [0,1] \times Z \mapsto Z$ transporting samples from a simple prior distribution $p_0$--e.g., a standard Gaussian--to a more complex, potentially unknown data distribution $p_1$ over time. Note how FM models time $t \in [0,1]$ to be varying continuously while moving away *from* an easy-to-sample distribution $p_0$ *towards* the unknown data-distribution, $p_1$. This results in a continuous and deterministic trajectory for each sample, which can be more efficient to generate compared to the stochastic paths of DMs. Formally, FM can be fully characterized by an ordinary differential equation (ODE) relating instantaneous variations of flows with the underlying vector field, and hence providing complete trajectories over the distributions’ support when integrating over time, +``` math +\begin{align} + \frac{d}{dt} \psi(z, t) &= v(t, \psi(t, z)) \\ + \psi(0, z) &= z +\end{align} +``` FM proved very effective in a variety of applications, ranging from image @esserScalingRectifiedFlow2024 and video generation @polyakMovieGenCast2025 to robotics control @blackp0VisionLanguageActionFlow2024. Most notably, in their introductory work on FM for GM, @lipmanFlowMatchingGenerative2023 show how DMs can be seen as a specific instance of FM where the *conditional* target vector field $u$ approximated by the noise regressor corresponds to + ``` math \htmlId{fm-diffusion-vector-field}{u(t, z\vert z_0) = \frac{\frac{d}{dt}\alpha(1-t)}{1 - (\alpha(1-t))^2}(\alpha(1-t)z - z_0), \quad \alpha(t) = e^{-\frac12 \int_0^t \beta(s) ds}, \quad \forall z_0 \in \mathcal D} ``` @@ -935,7 +1011,7 @@ Note that the traditional discrete-time noise-scheduler ${\beta_t}_{t=0}^T$ is n layout="fixed" alt="Figure" /> -

<span id="ch4-normalizing-flows" style="position: absolute;"></span>

+
Probability distributions can be modified applying vector fields resulting in a flow of mass in the support. When acting over time, vector fields can effectively change the distribution’s structure.
@@ -949,11 +1025,19 @@ While the noising schedule of DMs results in a stochastic process that resembles layout="fixed" alt="Figure" /> -

<span id="ch4-diffusion-paths-versus-fm" style="position- absolute;"></span>

+
Compared to diffusion, flow matching distorts distribution along a less randomic pattern, resulting in a clearer interpolation between source and target distribution. The visualization shows an example comparison between these two methods on joint distribution of robot observations and actions over T = 50 steps.
-In practice, FM can be applied to generative modeling by learning a vector field regressor $v_\theta(z, t)$ to approximate a given target vector field $u(t, z)$. In the particular case of DMs, $u(t, z)$ is defined as in [fm-diffusion-vector-field], while in priciple the target vector field can be learned to induce a particular transportation, or fixed according to OT. Given a sample from the data distribution $z_1 \sim p_1$ and a sample from an easy-to-sample prior $z_0 \sim p_0$, CFM defines a simple path between them using *linear interpolation* between samples $z_t = (1-t)z_0 + t z_1$, resulting in the target vector field $u(t, z_t) = z_1 - z_0$. Then, a FM model can be trained with the simple regression objective defined as $\htmlId{flow-matching-objective}{\mathcal L(\theta) = \mathbb{E}_{t, z_0, z_1} \big[ \Vert v_\theta((1-t)z_0 + t z_1, t) - (z_1 - z_0) \Vert^2 \big], \quad t \sim \mathcal{U}([0,1]),}$ where $z_0 \sim p_0(\bullet)$ and $z_1 \sim p_1(\bullet)$. Note how in [flow-matching-objective]--differently from [diffusion-simplified-loss]--time is assumed to be varying continuously $t \sim \mathcal U([0,1])$ rather than discretely $t \sim \mathcal U(\{0,1\})$, a key property of flow-based models. The objective in [flow-matching-objective] directly regresses the learned vector field onto the simple, straight path connecting a point from the prior and a point from the data, providing a simulation-free training procedure that is both stable and efficient. At inference time, samples are generated by starting with $z_0 \sim p_0$ and iteratively refined according to $\frac{dz}{dt} = v_\theta(z_t, t)$ for $t \in [0,1]$--an operation that can be numerically carried out with standard ODE solvers. +In practice, FM can be applied to generative modeling by learning a vector field regressor $v_\theta(z, t)$ to approximate a given target vector field $u(t, z)$. In the particular case of DMs, $u(t, z)$ is defined as in [fm-diffusion-vector-field], while in priciple the target vector field can be learned to induce a particular transportation, or fixed according to OT. Given a sample from the data distribution $z_1 \sim p_1$ and a sample from an easy-to-sample prior $z_0 \sim p_0$, CFM defines a simple path between them using *linear interpolation* between samples $z_t = (1-t)z_0 + t z_1$, resulting in the target vector field $u(t, z_t) = z_1 - z_0$. Then, a FM model can be trained with the simple regression objective defined as +``` math +\begin{align} + + \mathcal L(\theta) = \mathbb{E}_{t, z_0, z_1} \big[ + \Vert v_\theta((1-t)z_0 + t z_1, t) - (z_1 - z_0) \Vert^2 \big], \quad t \sim \mathcal{U}([0,1]), +\end{align} +``` +where $z_0 \sim p_0(\bullet)$ and $z_1 \sim p_1(\bullet)$. Note how in [flow-matching-objective]--differently from [diffusion-simplified-loss]--time is assumed to be varying continuously $t \sim \mathcal U([0,1])$ rather than discretely $t \sim \mathcal U(\{0,1\})$, a key property of flow-based models. The objective in [flow-matching-objective] directly regresses the learned vector field onto the simple, straight path connecting a point from the prior and a point from the data, providing a simulation-free training procedure that is both stable and efficient. At inference time, samples are generated by starting with $z_0 \sim p_0$ and iteratively refined according to $\frac{dz}{dt} = v_\theta(z_t, t)$ for $t \in [0,1]$--an operation that can be numerically carried out with standard ODE solvers. ### Action Chunking with Transformers @@ -961,7 +1045,17 @@ While GMs prove useful in learning complex, high-dimensional multi-modal distrib On the robot learning side of their contributions, @zhaoLearningFineGrainedBimanual2023 adopt transformers as the architectural backbone to learn a *Conditional* VAE @sohnLearningStructuredOutput2015. Conditional VAEs are a variation of the more standard VAE formulation introducing a conditioning variable on sampling from the latent prior, allowing the modeling of *one-to-many* relationships between latent and data samples. Further, in stark contrast with previous work @florenceImplicitBehavioralCloning2022, @jannerPlanningDiffusionFlexible2022, @zhaoLearningFineGrainedBimanual2023 do not learn a full joint $p_\theta(o,a)$ on observation and actions. While the *policy* distribution $p_\theta(a \vert o)$ can in principle be entirely described from its joint $p_\theta(o,a)$, it is often the case that the conditional distribution is intractable when using function approximators, as $p_\theta(a \vert o) = \tfrac{p_\theta(o,a)}{\int_\mathcal Ap_\theta(o,a)}$ and the integral in the denominator is typically intractable. Instead of modeling the full joint using a vanilla VAE, @zhaoLearningFineGrainedBimanual2023 propose learning a *conditional* VAE @sohnLearningStructuredOutput2015 modeling the policy distribution directly $p (a \vert o)$. -In practice, when learning from demonstrations adopting CVAEs results in a slight modification to the VAE objective in [ELBO], which is adapted to $\htmlId{c-ELBO}{\text{ELBO}_{\mathcal D}(\theta, \phi, \omega) = \sum_{i=0}^{N} \left( \mathbb{E}_{z \sim q_\phi(\cdot \vert o_i, a_i)} \big[ \log p_\theta(a_i \vert z, o_i) \big] - \text{D}_{\text{KL}}\big[ q_\phi(z \vert o_i, a_i) \Vert p_\omega(z \vert o_i) \big] \right)}$ Notice how in [c-ELBO] we are now also learning a new set of parameters $\omega$ for the prior distribution in the latent space. Effectively, this enables conditioning latent-space sampling (and thus reconstruction) during training, and potentially inference, providing useful when learning inherently conditional distributions like policies. Further, ACT is trained as a $\beta$-CVAE @higgins2017beta, using a weight of the KL regularization term in [c-ELBO] as an hyperparameter regulating the information condensed in the latent space, where higher $\beta$ results in a less expressive latent space. +In practice, when learning from demonstrations adopting CVAEs results in a slight modification to the VAE objective in [ELBO], which is adapted to +``` math +\begin{align} + + \text{ELBO}_{\mathcal D}(\theta, \phi, \omega) = \sum_{i=0}^{N} \left( + \mathbb{E}_{z \sim q_\phi(\cdot \vert o_i, a_i)} \big[ \log p_\theta(a_i \vert z, o_i) \big] + - \text{D}_{\text{KL}}\big[ q_\phi(z \vert o_i, a_i) \Vert p_\omega(z \vert o_i) \big] + \right) +\end{align} +``` +Notice how in [c-ELBO] we are now also learning a new set of parameters $\omega$ for the prior distribution in the latent space. Effectively, this enables conditioning latent-space sampling (and thus reconstruction) during training, and potentially inference, providing useful when learning inherently conditional distributions like policies. Further, ACT is trained as a $\beta$-CVAE @higgins2017beta, using a weight of the KL regularization term in [c-ELBO] as an hyperparameter regulating the information condensed in the latent space, where higher $\beta$ results in a less expressive latent space. In their work, @zhaoLearningFineGrainedBimanual2023 ablated using a GM to learn from human demonstrations compared to a simpler, supervised objective, $\mathcal L_1(a,a^\prime) = \Vert a - a^\prime \Vert_1$. Interestingly, they found the performance of these two approaches to be comparable when learning from *scripted* demonstrations. That is, when learning from data collected rolling out a predetermined set of commands $[q^c_0, q^c_1, \dots]$, GM did *not* prove competitive compared to standard supervised learning. However, when learning from human demonstrations--i.e., from data collected executing commands coming from a human controller $[q^h_0, q^h_1, \dots]$--they found performance (success rate on a downstream task) to be severily (-33.3%) hindered from adopting a standard supervised learning objective compared to a richer, potentially more complex to learn variational objective, in keeping with the multimodal nature of human demonstrations data and findings presented in @florenceImplicitBehavioralCloning2022. The authors also ablate the action chunking paradigm, reporting significant performance gains for performing action chunking (1% vs. 44% success rate). To avoid acting openloop, @zhaoLearningFineGrainedBimanual2023 design an inference process consisting in performing inference at every timestep $t$ and then aggregate overlapping chunks using chunks’ exponential moving average. @@ -973,7 +1067,7 @@ In their work, @zhaoLearningFineGrainedBimanual2023 ablated using a GM to learn layout="fixed" alt="Figure" /> -

<span id="ch4-act" style="position: absolute;"></span>

+
Action Chunking with Transformer (ACT), as in @zhaoLearningFineGrainedBimanual2023. ACT introduces an action chunking paradigm to cope with high-dimensional multi-modal demonstration data, and a transformer-based CVAE architecture.
@@ -987,7 +1081,7 @@ In ACT (Figure  -

<span id="ch4-act-encoder" style="position: absolute;"></span>

+
The CVAE encoder used in ACT. Input action chunks are first embedded and aggregated with positional embeddings, before being processed alongside embedded proprioperceptive information, and a learned [CLS] token used to aggregate input level information, and predict the style variable z . The encoder is entirely disregarded at inference time.
@@ -1001,7 +1095,7 @@ However, the authors claim using a deterministic procedure to derive $z$ may ben layout="fixed" alt="Figure" /> -

<span id="ch4-act-decoder" style="position: absolute;"></span>

+
The CVAE decoder used in ACT, comprising of a full encoder-decoder Transformer architecture. Camera observations from all n camera views are first embedded using pre-trained visual encoders, and then concatenated to the corresponding positional embeddings. Then, alongside embeddings for the proprioperceptive information available and the style variable z retrieved from the CVAE encoder, the Transformer encoder shares the matrices K, Q with the Transformer decoder, trained to decode fixed position embeddings into action valid chunks.
@@ -1011,13 +1105,17 @@ However, the authors claim using a deterministic procedure to derive $z$ may ben DMs proved very effective in approximating complex highly dimensional distributions, such as distributions over images @hoDenoisingDiffusionProbabilistic2020 or videos @polyakMovieGenCast2025, thanks to their inherent capability to deal with multimodal data and training stability. In Diffusion Policy (DP), @chiDiffusionPolicyVisuomotor2024 present an application of DMs the field of robot learning, leveraging diffusion to model human expert demonstrations in a variety of simulated and real-world tasks. Similarily to Action Chunking with Transformer @zhaoLearningFineGrainedBimanual2023, @chiDiffusionPolicyVisuomotor2024 (1) adopt a modified *observation-conditioned target distribution* instead of the full joint $p(o,a)$ and (2) predict multiple actions into the future instead of a single action. Besides the intractability of the observations’ marginal $p_\theta(o)$ given $p_\theta(o,a)$, DP’s rationale for modeling the data distribution via $p_\theta(a \vert o)$ stems from the rather test-time compute intensive nature of diffusion, whereby generating actions *alongside* observations is likely to result in higher complexity and thus a likely larger number of denoising operations, which would prove ultimately pointless considering robotics applications rely on the capability to generate controls rather than reproducing observations. -In practice, conditioning on observation data is achieved conditioning the added noise regressor $\epsilon_\theta$ introduced in 
[diffusion-simplified-loss] on a stack of $T_o$ observations, resulting in the *conditional* simplified diffusion objective - -$$ -`\htmlId{diffusion-policy-objective}{\mathcal L(\theta) = \mathbb{E}_{t, a_{t:t+H_a}, \epsilon} \big[ \Vert \epsilon - \epsilon_\theta(\sqrt{\bar \alpha_t} a_{t:t+T_a} + \epsilon \sqrt{1 - \bar \alpha_t}, t, o_{t-T_o:t}) \Vert^2 \big],\\ t \sim \mathcal{U}(\{1,\dots,T\}), \quad a_{t:t+T_a}, o_{t-T_o:t} \sim \mathcal{D}, \quad \epsilon \sim \mathcal{N}(\mathbf{0},\mathbf{I}). \notag}` -$$ - - Notice how in [diffusion-policy-objective] the noise regressor is conditioned both on the latent variable rank $t$ *and* on a stack of previous observations $o_{t-T_o-t}$.  @chiDiffusionPolicyVisuomotor2024 claim the combination of (1) conditioning on a horizon of previous observations and (2) predicting multiple actions into the future allows DP to *commit to specific modes* in the data at inference time, which proves essential for good performance and avoiding undecisiveness. +In practice, conditioning on observation data is achieved conditioning the added noise regressor $\epsilon_\theta$ introduced in [diffusion-simplified-loss] on a stack of $T_o$ observations, resulting in the *conditional* simplified diffusion objective +``` math +\begin{align} + \mathcal L(\theta) &= \mathbb{E}_{t, a_{t:t+H_a}, \epsilon} \big[ + \Vert \epsilon - \epsilon_\theta(\sqrt{\bar \alpha_t} a_{t:t+T_a} + \epsilon \sqrt{1 - \bar \alpha_t}, t, o_{t-T_o:t}) \Vert^2 \big], \\ + & t \sim \mathcal{U}(\{1,\dots,T\}), \quad + a_{t:t+T_a}, o_{t-T_o:t} \sim \mathcal{D}, \quad + \epsilon \sim \mathcal{N}(\mathbf{0},\mathbf{I}). \notag +\end{align} +``` +Notice how in [diffusion-policy-objective] the noise regressor is conditioned both on the latent variable rank $t$ *and* on a stack of previous observations $o_{t-T_o-t}$.  @chiDiffusionPolicyVisuomotor2024 claim the combination of (1) conditioning on a horizon of previous observations and (2) predicting multiple actions into the future allows DP to *commit to specific modes* in the data at inference time, which proves essential for good performance and avoiding undecisiveness.
-

<span id="diffusion-policy-architecture" style="position: absolute;"></span>

+
The Diffusion Policy archicture, as in @chiDiffusionPolicyVisuomotor2024. A stack of H o previous observations is used as external conditioning to denoise a group of H a actions. Conditioning is used at every layer of a U-Net block, and in practice allows to obtain fully-formed action chunks with as little as T = 10 denoising steps.
@@ -1058,7 +1156,7 @@ We directly assess the lack of adaptiveness of robot systems due to acting open- layout="fixed" alt="Figure" /> -

<span id="ch4-async-inference" style="position: absolute;"></span>

+
Asynchronous inference. Illustration of the asynchronous inference stack. Note that the policy can be run on a remote server, possibly with GPUs.
@@ -1101,7 +1199,7 @@ Interestingly, the behavior of async inference can be studied analytically. Firs layout="fixed" alt="Figure" /> -

<span id="ch4-queues" style="position: absolute;"></span>

+
Action queue size evolution at runtime for various levels of g when (A) not filtering out observation based on joint-space similarity and (B) filtering out near-duplicates observation, measuring their similarity in joint-space.
@@ -1135,7 +1233,7 @@ The advent of large models trained on internet-scale datasets has drastically in layout="fixed" alt="Figure" /> -

<span id="ch5-ml-vs-robotics-foundation" style="position: absolute;"></span>

+
Fields within ML such as Computer Vision and NLP converged on the development of foundation models, trained on a variety of large scale models and capable to perform multiple downstream tasks (top). Conversely, robotics suffered from limited standardization in terms of the architectures used, and siloed, task specific datasets, incurring in a high degree of fragmentation which traditionally hindered the development of generalist models for robotics in favour of task-specific models (bottom).
@@ -1151,7 +1249,7 @@ The remarkable success of foundation models in NLP and CV is predicated on two c layout="fixed" alt="Figure" /> -

<span id="ch5-generalist-policies-timeline" style="position: absolute;"></span>

+
Early efforts in the development of generalist models for robotics include BC-Zero @jangBCZZeroShotTask2022, RT-1 @brohanRT1RoboticsTransformer2023, and RT-2 @brohanRT2VisionLanguageActionModels2023: large scale models trained on thousands of demonstrations. The open release of the Open-X @collaborationOpenXEmbodimentRobotic2025 and DROID datasets @khazatskyDROIDLargeScaleInTheWild2025 fostered the development of open source models: OpenVLA @kimOpenVLAOpenSourceVisionLanguageAction2024, π 0 @blackp0VisionLanguageActionFlow2024 and SmolVLA @shukorSmolVLAVisionLanguageActionModel2025.
@@ -1171,7 +1269,7 @@ The success of large, proprietary models like RT-1 and RT-2, highlighted a growi layout="fixed" alt="Figure" /> -

<span id="ch5-trends" style="position: absolute;"></span>

+
Robot learning is undergoing a paradigmatic shift: centralized data collections (A, left) are increasingly larger, often comprising Ms of demonstrations, and (A, right) decentralized approaches to data collection are also rising as an alternative for large scale data collection. (B) Generalist models are also becoming increasingly smaller and easier to run on limited hardware.
@@ -1201,19 +1299,30 @@ $\pi_0$ @blackp0VisionLanguageActionFlow2024 introduce a VLA consisting of a Mo layout="fixed" alt="Figure" /> -

<span id="ch5-pi0" style="position: absolute;"></span>

+
The π 0 architecture, as in @blackp0VisionLanguageActionFlow2024. Vision and language tokens are routed to a VLM backbone which is prevented from attending robot proprioperceptive states and action tokens, which are instead routed to a smaller subset of weights within the architecture. The architecture is trained with Flow Matching on 10M+ trajectories from a mixture of closed and openly available datasets.
Concretely, $\pi_0$ is a unified transformer with two disjoint sets of weights $\phi, \theta$. A larger VLM backbone $p_\phi$ initialized from Gemma 2.6B processes multiple image frames obtained from multiple cameras points $[\{ I_t \}_{t=1}^n]$, as well as a language instruction $[\ell_t]$ used to describe the task considered. Concurrently, a 300M-parameter *action expert* based on a similar transformer architecture is used processes the robot proprioperceptive state $q_t$ and an action chunk $a_{t:t+H_a}$ (Figure [ch5-pi0]). The different expert networks operate separately in processing the respective inputs and turning them into query, key and value matrices, and only share information between each other via self-attention layers. The outputs from the VLM backbone are disregarded, while the vector field regressed by the action expert is used to iteratively refine the action process. In particular, $\pi_0$uses a *blockwise causal attention mask* over tokens belonging to three separate blocks: (1) image and language tokens $\mathcal T_i$ obtained from $[\{ I_t \}_{t=1}^n, \ell_t]$, (2) proprioperceptive tokens $\mathcal T_q$ obtained from $q_t$, and (3) the action tokens $\mathcal T_a$ for items in the chunk $a^{\tau}_{t:t+H_a}$ at time $\tau$ in the flow-matching process. Notably, *within* each block the attention operations are bidirectional, while across blocks, future blocks are masked out. Formally, this corresponds to using the attention mask $\mathbf{A} = \bordermatrix{ \mathcal{T}_i \mathcal{T}_q \mathcal{T}_a \cr \mathcal{T}_i \mathbf{1} \mathbf{0} \mathbf{0} \cr \mathcal{T}_q \mathbf{1} \mathbf{1} \mathbf{0} \cr \mathcal{T}_a \mathbf{1} \mathbf{1} \mathbf{1} \cr }, \quad \mathbf{1}: \text{Bidirectional Attention}, \ \mathbf{0}: \text{Masked Attention}$ Note how *intra*-block directional attention allows tokens to communicate freely, while *inter*-block communication is mediated by the attention mask $\mathbf{A}$. *Blockwise causal masking* effectively prevents the pre-trained perception-language tokens from attending to robotics-tokens, likely out of distribution for VLM backbones traditionally trained on large corpora of internet, non-robotics, data. Crucially, because communication is obstructed between image-language tokens, proprioperceptive and action tokens, one can cache keys and values across denoising steps at runtime time, incuring in a reduced computational footprint and faster inference. -In $\pi_0$, both the VLM backbone and action expert are update using a *flow matching* loss, and in particular are updated minimizing: +In $\pi_0$, both the VLM backbone and action expert are update using a *flow matching* loss, and in particular are updated minimizing: +``` math +\begin{align} + \mathcal{L}(\phi, \theta) &= + \mathbb{E}_{\tau, \epsilon, o_t, a_{t:t+H_a}}\Big[ + \big\Vert + v_\theta(\underbrace{\tau a_{t:t+H_a} + (1-\tau) \epsilon}_{\tilde a_{t:t+H_a}},\, o_t,\, \tau) + - (\epsilon - a_{t:t+H_a}) + \big\Vert^2 + \Big], \\ + &\tau \sim \mathrm{Beta}_{[0,s]}(1.5,1), \quad + \epsilon \sim \mathcal{N}(\mathbf{0}, \mathbf{I}), \quad + o_t, a_{t:t+H_a} \sim \mathcal D \notag +\end{align} +``` -$$ -`\htmlId{pi0-loss}{\mathcal{L}(\phi, \theta) = \mathbb{E}_{\tau, \epsilon, o_t, a_{t:t+H_a}}\Big[ \big\Vert v_\theta(\underbrace{\tau a_{t:t+H_a} + (1-\tau) \epsilon}_{\tilde a_{t:t+H_a}},\, o_t,\, \tau) - (\epsilon - a_{t:t+H_a}) \big\Vert^2 \Big],\\ \tau \sim \mathrm{Beta}_{[0,s]}(1.5,1), \quad \epsilon \sim \mathcal{N}(\mathbf{0}, \mathbf{I}), \quad o_t, a_{t:t+H_a} \sim \mathcal D \notag}` -$$ +Where the experts parametrized by the separate weights $\phi, \theta$ interact with each other via self-attention layers only, so that the action expert $v_\theta$ internal computations also depend on the VLM backbone’s parameters $\phi$. Importantly, @blackp0VisionLanguageActionFlow2024 minimize [pi0-loss] over both the multimodal backbone and action expert parameters, thus updating the internal representations of the VLM using BC-specific gradients. In contrast, @driessKnowledgeInsulatingVisionLanguageAction2025 later show that failing to insulate the VLM knowledge from the flow matching gradients actually harms performance. Inference is performed iteratively refining action chunks while numerically forward-integrating the vector field predicted by the action expert, - Where the experts parametrized by the separate weights $\phi, \theta$ interact with each other via self-attention layers only, so that the action expert $v_\theta$ internal computations also depend on the VLM backbone’s parameters $\phi$. Importantly, @blackp0VisionLanguageActionFlow2024 minimize [pi0-loss] over both the multimodal backbone and action expert parameters, thus updating the internal representations of the VLM using BC-specific gradients. In contrast, @driessKnowledgeInsulatingVisionLanguageAction2025 later show that failing to insulate the VLM knowledge from the flow matching gradients actually harms performance. Inference is performed iteratively refining action chunks while numerically forward-integrating the vector field predicted by the action expert, ``` math \begin{equation} a_{t:t+H_a}^{\tau + \delta} = a_{t:t+H_a}^{\tau } + \delta v_\theta(a_{t:t+H_a}^{\tau }, o_t) @@ -1255,7 +1364,7 @@ VLAs remain in an early stage of development and are not yet as mature or widely layout="fixed" alt="Figure" /> -

<span id="ch5-smolvla" style="position: absolute;"></span>

+
The SmolVLA architecture, as in @shukorSmolVLAVisionLanguageActionModel2025. SmolVLA is a compact MoE model trained with flow matching to denoise action chunks. Vision and language tokens are fed to a VLM backbone, and share information with the proprioperceptive and action tokens via the attention mechanism. The attention expert interleaves SA and CA layers for further conditioning on the visual features from the VLM backbone. SmolVLA skips computations and reduces the visual tokens, resulting in 6x less memory usage than π 0 .
diff --git a/app/scripts/latex-to-markdown/package-lock.json b/app/scripts/latex-to-mdx/package-lock.json similarity index 100% rename from app/scripts/latex-to-markdown/package-lock.json rename to app/scripts/latex-to-mdx/package-lock.json diff --git a/app/scripts/latex-to-markdown/package.json b/app/scripts/latex-to-mdx/package.json similarity index 65% rename from app/scripts/latex-to-markdown/package.json rename to app/scripts/latex-to-mdx/package.json index 38618b31ca768a7c349ebe774ae58e31196bd6b1..16850d0a30f47e351a3303970a7ce3a2a881abb5 100644 Binary files a/app/scripts/latex-to-markdown/package.json and b/app/scripts/latex-to-mdx/package.json differ diff --git a/app/scripts/latex-to-markdown/post-processor.mjs b/app/scripts/latex-to-mdx/post-processor.mjs similarity index 82% rename from app/scripts/latex-to-markdown/post-processor.mjs rename to app/scripts/latex-to-mdx/post-processor.mjs index 262404ed25cd8a683f7e2c88513a9f183db5205b..be91e72eda5e8d2d0856871251b76d706eb1751f 100644 --- a/app/scripts/latex-to-markdown/post-processor.mjs +++ b/app/scripts/latex-to-mdx/post-processor.mjs @@ -68,6 +68,45 @@ function fixMathCommands(content) { .replace(/\\vdots/g, '\\vdots'); // This one should be fine, but kept for consistency } +/** + * Convert LaTeX matrix commands to KaTeX-compatible environments + * @param {string} content - Markdown content + * @returns {string} - Content with fixed matrix commands + */ +function fixMatrixCommands(content) { + console.log(' 🔢 Converting matrix commands to KaTeX format...'); + + let fixedCount = 0; + + // Convert \pmatrix{...} to \begin{pmatrix}...\end{pmatrix} + content = content.replace(/\\pmatrix\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/g, (match, matrixContent) => { + fixedCount++; + // Split by \\ for rows, handle nested braces + const rows = matrixContent.split('\\\\').map(row => row.trim()).filter(row => row); + return `\\begin{pmatrix}\n${rows.join(' \\\\\n')}\n\\end{pmatrix}`; + }); + + // Convert \bmatrix{...} to \begin{bmatrix}...\end{bmatrix} + content = content.replace(/\\bmatrix\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/g, (match, matrixContent) => { + fixedCount++; + const rows = matrixContent.split('\\\\').map(row => row.trim()).filter(row => row); + return `\\begin{bmatrix}\n${rows.join(' \\\\\n')}\n\\end{bmatrix}`; + }); + + // Convert \vmatrix{...} to \begin{vmatrix}...\end{vmatrix} + content = content.replace(/\\vmatrix\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/g, (match, matrixContent) => { + fixedCount++; + const rows = matrixContent.split('\\\\').map(row => row.trim()).filter(row => row); + return `\\begin{vmatrix}\n${rows.join(' \\\\\n')}\n\\end{vmatrix}`; + }); + + if (fixedCount > 0) { + console.log(` ✅ Fixed ${fixedCount} matrix command(s)`); + } + + return content; +} + /** * Fix Unicode characters that break MDX/JSX parsing * @param {string} content - Markdown content @@ -102,13 +141,18 @@ function fixMultilineMath(content) { console.log(' 📏 Fixing multiline math expressions for MDX...'); return content - // Convert multiline inline math to display math blocks - .replace(/\$([^$]*?\\\\[^$]*?)\$/gs, (match, mathContent) => { - // Remove leading/trailing whitespace and normalize newlines - const cleanedMath = mathContent - .replace(/^\s+|\s+$/g, '') - .replace(/\s*\\\\\s*/g, '\\\\\n '); - return `$$\n${cleanedMath}\n$$`; + // Convert multiline inline math to display math blocks (more precise regex) + // Only match if the content is a self-contained math expression within a single line + .replace(/\$([^$\n]*\\\\[^$\n]*)\$/g, (match, mathContent) => { + // Only convert if it contains actual math operators and line breaks + if (mathContent.includes('\\\\') && /[=+\-*/^_{}]/.test(mathContent)) { + // Remove leading/trailing whitespace and normalize newlines + const cleanedMath = mathContent + .replace(/^\s+|\s+$/g, '') + .replace(/\s*\\\\\s*/g, '\\\\\n '); + return `$$\n${cleanedMath}\n$$`; + } + return match; // Keep original if it doesn't look like multiline math }) // Ensure display math blocks are properly separated .replace(/\$\$\s*\n\s*([^$]+?)\s*\n\s*\$\$/g, (match, mathContent) => { @@ -272,6 +316,7 @@ export function postProcessMarkdown(content, inputDir = null) { processedContent = simplifyLatexDelimiters(processedContent); processedContent = removeOrphanedLabels(processedContent); processedContent = fixMathCommands(processedContent); + processedContent = fixMatrixCommands(processedContent); processedContent = fixUnicodeIssues(processedContent); processedContent = fixMultilineMath(processedContent); processedContent = fixAllAttributes(processedContent); diff --git a/app/scripts/latex-to-markdown/reference-preprocessor.mjs b/app/scripts/latex-to-mdx/reference-preprocessor.mjs similarity index 100% rename from app/scripts/latex-to-markdown/reference-preprocessor.mjs rename to app/scripts/latex-to-mdx/reference-preprocessor.mjs diff --git a/app/src/components/Hero.astro b/app/src/components/Hero.astro index 6484663672e6815fea4e9b8e3881e3255a97bc09..454b0d83584b1d19c4b0148dce5d1da9796dbb5b 100644 --- a/app/src/components/Hero.astro +++ b/app/src/components/Hero.astro @@ -5,26 +5,49 @@ interface Props { title: string; // may contain HTML (e.g.,
) titleRaw?: string; // plain title for slug/PDF (optional) description?: string; - authors?: Array; + authors?: Array< + string | { name: string; url?: string; affiliationIndices?: number[] } + >; affiliations?: Array<{ id: number; name: string; url?: string }>; affiliation?: string; // legacy single affiliation published?: string; doi?: string; } -const { title, titleRaw, description, authors = [], affiliations = [], affiliation, published, doi } = Astro.props as Props; +const { + title, + titleRaw, + description, + authors = [], + affiliations = [], + affiliation, + published, + doi, +} = Astro.props as Props; type Author = { name: string; url?: string; affiliationIndices?: number[] }; -function normalizeAuthors(input: Array): Author[] { +function normalizeAuthors( + input: Array< + | string + | { + name?: string; + url?: string; + link?: string; + affiliationIndices?: number[]; + } + >, +): Author[] { return (Array.isArray(input) ? input : []) .map((a) => { - if (typeof a === 'string') { + if (typeof a === "string") { return { name: a } as Author; } - const name = (a?.name ?? '').toString(); + const name = (a?.name ?? "").toString(); const url = (a?.url ?? a?.link) as string | undefined; - const affiliationIndices = Array.isArray((a as any)?.affiliationIndices) ? (a as any).affiliationIndices : undefined; + const affiliationIndices = Array.isArray((a as any)?.affiliationIndices) + ? (a as any).affiliationIndices + : undefined; return { name, url, affiliationIndices } as Author; }) .filter((a) => a.name && a.name.trim().length > 0); @@ -35,35 +58,41 @@ const normalizedAuthors: Author[] = normalizeAuthors(authors as any); // Determine if affiliation superscripts should be shown (only when there are multiple distinct affiliations referenced by authors) const authorAffiliationIndexSet = new Set(); for (const author of normalizedAuthors) { - const indices = Array.isArray(author.affiliationIndices) ? author.affiliationIndices : []; + const indices = Array.isArray(author.affiliationIndices) + ? author.affiliationIndices + : []; for (const idx of indices) { - if (typeof idx === 'number') { + if (typeof idx === "number") { authorAffiliationIndexSet.add(idx); } } } const shouldShowAffiliationSupers = authorAffiliationIndexSet.size > 1; -const hasMultipleAffiliations = Array.isArray(affiliations) && affiliations.length > 1; +const hasMultipleAffiliations = + Array.isArray(affiliations) && affiliations.length > 1; function stripHtml(text: string): string { - return String(text || '').replace(/<[^>]*>/g, ''); + return String(text || "").replace(/<[^>]*>/g, ""); } function slugify(text: string): string { - return String(text || '') - .normalize('NFKD') - .replace(/\p{Diacritic}+/gu, '') - .toLowerCase() - .replace(/[^a-z0-9]+/g, '-') - .replace(/^-+|-+$/g, '') - .slice(0, 120) || 'article'; + return ( + String(text || "") + .normalize("NFKD") + .replace(/\p{Diacritic}+/gu, "") + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, "") + .slice(0, 120) || "article" + ); } const pdfBase = titleRaw ? stripHtml(titleRaw) : stripHtml(title); const pdfFilename = `${slugify(pdfBase)}.pdf`; --- +
-

+

{description &&

{description}

} @@ -72,53 +101,81 @@ const pdfFilename = `${slugify(pdfBase)}.pdf`;
- {normalizedAuthors.length > 0 && ( -
-

Author{normalizedAuthors.length > 1 ? 's' : ''}

-
    - {normalizedAuthors.map((a, i) => { - const supers = shouldShowAffiliationSupers && Array.isArray(a.affiliationIndices) && a.affiliationIndices.length - ? {a.affiliationIndices.join(',')} - : null; - return ( -
  • - {a.url ? {a.name} : a.name}{supers} -
  • - ); - })} -
-
- )} - {(Array.isArray(affiliations) && affiliations.length > 0) && ( -
-

Affiliation{affiliations.length > 1 ? 's' : ''}

- {hasMultipleAffiliations ? ( -
    - {affiliations.map((af) => ( -
  1. {af.url ? {af.name} : af.name}
  2. - ))} -
- ) : ( -

- {affiliations[0]?.url - ? {affiliations[0].name} - : affiliations[0]?.name} -

- )} -
- )} - {(!affiliations || affiliations.length === 0) && affiliation && ( -
-

Affiliation

-

{affiliation}

-
- )} - {published && ( -
-

Published

-

{published}

-
- )} + { + normalizedAuthors.length > 0 && ( +
+

Author{normalizedAuthors.length > 1 ? "s" : ""}

+
    + {normalizedAuthors.map((a, i) => { + const supers = + shouldShowAffiliationSupers && + Array.isArray(a.affiliationIndices) && + a.affiliationIndices.length ? ( + {a.affiliationIndices.join(",")} + ) : null; + return ( +
  • + {a.url ? {a.name} : a.name} + {supers} +
  • + ); + })} +
+
+ ) + } + { + Array.isArray(affiliations) && affiliations.length > 0 && ( +
+

Affiliation{affiliations.length > 1 ? "s" : ""}

+ {hasMultipleAffiliations ? ( +
    + {affiliations.map((af) => ( +
  1. + {af.url ? ( + + {af.name} + + ) : ( + af.name + )} +
  2. + ))} +
+ ) : ( +

+ {affiliations[0]?.url ? ( + + {affiliations[0].name} + + ) : ( + affiliations[0]?.name + )} +

+ )} +
+ ) + } + { + (!affiliations || affiliations.length === 0) && affiliation && ( +
+

Affiliation

+

{affiliation}

+
+ ) + } + { + published && ( +
+

Published

+

{published}

+
+ ) + } + +
+
+ + diff --git a/app/src/content/embeds/original_embeds/plotly/banner.py b/app/src/content/embeds/original_embeds/plotly/banner.py new file mode 100644 index 0000000000000000000000000000000000000000..73841e91b8ae9a0e1d4dcca0e6534b507b0baabe --- /dev/null +++ b/app/src/content/embeds/original_embeds/plotly/banner.py @@ -0,0 +1,134 @@ +import plotly.graph_objects as go +import numpy as np +import pandas as pd + +# Scene parameters (same ranges as the Astro integration) +cx, cy = 1.5, 0.5 # center +a, b = 1.3, 0.45 # max extent in x/y (ellipse for anisotropy) + +# Spiral galaxy parameters +num_points = 3000 # more dots +num_arms = 3 # number of spiral arms +num_turns = 2.1 # number of turns per arm +angle_jitter = 0.12 # angular jitter to fan out the arms +pos_noise = 0.015 # global position noise + +# Generate points along spiral arms (Archimedean spiral) +t = np.random.rand(num_points) * (2 * np.pi * num_turns) # progression along the arm +arm_indices = np.random.randint(0, num_arms, size=num_points) +arm_offsets = arm_indices * (2 * np.pi / num_arms) + +theta = t + arm_offsets + np.random.randn(num_points) * angle_jitter + +# Normalized radius (0->center, 1->edge). Power <1 to densify the core +r_norm = (t / (2 * np.pi * num_turns)) ** 0.9 + +# Radial/lateral noise that slightly increases with radius +noise_x = pos_noise * (0.8 + 0.6 * r_norm) * np.random.randn(num_points) +noise_y = pos_noise * (0.8 + 0.6 * r_norm) * np.random.randn(num_points) + +# Elliptic projection +x_spiral = cx + a * r_norm * np.cos(theta) + noise_x +y_spiral = cy + b * r_norm * np.sin(theta) + noise_y + +# Central bulge (additional points very close to the core) +bulge_points = int(0.18 * num_points) +phi_b = 2 * np.pi * np.random.rand(bulge_points) +r_b = (np.random.rand(bulge_points) ** 2.2) * 0.22 # compact bulge +noise_x_b = (pos_noise * 0.6) * np.random.randn(bulge_points) +noise_y_b = (pos_noise * 0.6) * np.random.randn(bulge_points) +x_bulge = cx + a * r_b * np.cos(phi_b) + noise_x_b +y_bulge = cy + b * r_b * np.sin(phi_b) + noise_y_b + +# Concatenation +x = np.concatenate([x_spiral, x_bulge]) +y = np.concatenate([y_spiral, y_bulge]) + +# Central intensity (for sizes/colors). 1 at center, ~0 at edge +z_spiral = 1 - r_norm +z_bulge = 1 - (r_b / max(r_b.max(), 1e-6)) # very bright bulge +z_raw = np.concatenate([z_spiral, z_bulge]) + +# Sizes: keep the 5..10 scale for consistency +sizes = (z_raw + 1) * 5 + +# Remove intermediate filtering: keep all placed points, filter at the very end + +df = pd.DataFrame({ + "x": x, + "y": y, + "z": sizes, # reused for size+color as before +}) + +def get_label(z): + if z < 0.25: + return "smol dot" + if z < 0.5: + return "ok-ish dot" + if z < 0.75: + return "a dot" + else: + return "biiig dot" + +# Labels based on central intensity +df["label"] = pd.Series(z_raw).apply(get_label) + +# Rendering order: small points first, big ones after (on top) +df = df.sort_values(by="z", ascending=True).reset_index(drop=True) + +fig = go.Figure() + +fig.add_trace(go.Scattergl( + x=df['x'], + y=df['y'], + mode='markers', + marker=dict( + size=df['z'], + color=df['z'], + colorscale=[ + [0, 'rgb(78, 165, 183)'], + [0.5, 'rgb(206, 192, 250)'], + [1, 'rgb(232, 137, 171)'] + ], + opacity=0.9, + ), + customdata=df[["label"]], + hovertemplate="Dot category: %{customdata[0]}", + hoverlabel=dict(namelength=0), + showlegend=False +)) + +fig.update_layout( + autosize=True, + paper_bgcolor='rgba(0,0,0,0)', + plot_bgcolor='rgba(0,0,0,0)', + showlegend=False, + margin=dict(l=0, r=0, t=0, b=0), + xaxis=dict( + showgrid=False, + zeroline=False, + showticklabels=False, + range=[0, 3] + ), + yaxis=dict( + showgrid=False, + zeroline=False, + showticklabels=False, + scaleanchor="x", + scaleratio=1, + range=[0, 1] + ) +) + +# fig.show() + +fig.write_html( + "../app/src/content/fragments/banner.html", + include_plotlyjs=False, + full_html=False, + config={ + 'displayModeBar': False, + 'responsive': True, + 'scrollZoom': False, + } +) \ No newline at end of file diff --git a/app/src/content/embeds/original_embeds/plotly/bar.py b/app/src/content/embeds/original_embeds/plotly/bar.py new file mode 100644 index 0000000000000000000000000000000000000000..4137fb1a2f90da2f7cf2ad1aec4bc15ca9c5720a --- /dev/null +++ b/app/src/content/embeds/original_embeds/plotly/bar.py @@ -0,0 +1,173 @@ +import plotly.graph_objects as go +import plotly.io as pio +import numpy as np + +""" +Stacked bar chart: GPU memory breakdown vs sequence length, with menus for Model Size and Recomputation. +Responsive, no zoom/pan, clean hover; styled to match the minimal theme. +""" + +# Axes +seq_labels = ["1024", "2048", "4096", "8192"] +seq_scale = np.array([1, 2, 4, 8], dtype=float) + +# Components and colors (aligned with the provided example) +components = [ + ("parameters", "rgb(78, 165, 183)"), + ("gradients", "rgb(227, 138, 66)"), + ("optimizer", "rgb(232, 137, 171)"), + ("activations", "rgb(206, 192, 250)"), +] + +# Model sizes and base memory (GB) for params/grad/opt (constant vs seq), by size +model_sizes = ["1B", "3B", "8B", "70B", "405B"] +params_mem = { + "1B": 4.0, + "3B": 13.3, + "8B": 26.0, + "70B": 244.0, + "405B": 1520.0, +} +# Optimizer ~= 2x params; gradients ~= params (illustrative) + +# Activations base coefficient per size (growth ~ coeff * (seq/1024)^2) +act_coeff = { + "1B": 3.6, + "3B": 9.3, + "8B": 46.2, + "70B": 145.7, + "405B": 1519.9, +} + +def activations_curve(size_key: str, recompute: str) -> np.ndarray: + base = act_coeff[size_key] * (seq_scale ** 2) + if recompute == "selective": + return base * 0.25 + if recompute == "full": + return base * (1.0/16.0) + return base + +def stack_for(size_key: str, recompute: str): + p = np.full_like(seq_scale, params_mem[size_key], dtype=float) + g = np.full_like(seq_scale, params_mem[size_key], dtype=float) + o = np.full_like(seq_scale, 2.0 * params_mem[size_key], dtype=float) + a = activations_curve(size_key, recompute) + return { + "parameters": p, + "gradients": g, + "optimizer": o, + "activations": a, + } + +# Precompute all combinations +recomp_modes = ["none", "selective", "full"] +Y = {mode: {size: stack_for(size, mode) for size in model_sizes} for mode in recomp_modes} + +# Build traces: 4 traces per size (20 total). Start with size index 0 visible +fig = go.Figure() +for size in model_sizes: + for comp_name, color in components: + fig.add_bar( + x=seq_labels, + y=Y["none"][size][comp_name], + name=comp_name, + marker=dict(color=color), + hovertemplate="Seq len=%{x}
Mem=%{y:.1f}GB
%{data.name}", + showlegend=True, + visible=(size == model_sizes[0]), + ) + +# Compute y-axis ranges per size and recomputation +def max_total(size: str, mode: str) -> float: + stacks = Y[mode][size] + totals = stacks["parameters"] + stacks["gradients"] + stacks["optimizer"] + stacks["activations"] + return float(np.max(totals)) + +layout_y_ranges = {mode: {size: 1.05 * max_total(size, mode) for size in model_sizes} for mode in recomp_modes} + +# Layout +fig.update_layout( + barmode="stack", + autosize=True, + paper_bgcolor="rgba(0,0,0,0)", + plot_bgcolor="rgba(0,0,0,0)", + margin=dict(l=40, r=28, t=20, b=40), + hovermode="x unified", + legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0), + xaxis=dict(title=dict(text="Sequence Length"), fixedrange=True), + yaxis=dict(title=dict(text="Memory (GB)"), fixedrange=True), +) + +# Updatemenus: Model Size (toggle visibility) +buttons_sizes = [] +for i, size in enumerate(model_sizes): + visible = [False] * (len(model_sizes) * len(components)) + start = i * len(components) + for j in range(len(components)): + visible[start + j] = True + buttons_sizes.append(dict( + label=size, + method="update", + args=[ + {"visible": visible}, + {"yaxis": {"range": [0, layout_y_ranges["none"][size]]}}, + ], + )) + +# Updatemenus: Recomputation (restyle y across all traces) +def y_for_mode(mode: str): + ys = [] + for size in model_sizes: + stacks = Y[mode][size] + for comp_name, _ in components: + ys.append(stacks[comp_name]) + return ys + +buttons_recomp = [] +for mode, label in [("none", "None"), ("selective", "selective"), ("full", "full")]: + ys = y_for_mode(mode) + # Flatten into the format expected by Plotly for multiple traces + buttons_recomp.append(dict( + label=label, + method="update", + args=[ + {"y": ys}, + {"yaxis": {"range": [0, max(layout_y_ranges[mode].values())]}}, + ], + )) + +fig.update_layout( + updatemenus=[ + dict( + type="dropdown", + x=1.03, xanchor="left", + y=0.60, yanchor="top", + showactive=True, + active=0, + buttons=buttons_sizes, + ), + dict( + type="dropdown", + x=1.03, xanchor="left", + y=0.40, yanchor="top", + showactive=True, + active=0, + buttons=buttons_recomp, + ), + ], + annotations=[ + dict(text="Model Size:", x=1.03, xanchor="left", xref="paper", y=0.60, yanchor="bottom", yref="paper", showarrow=False), + dict(text="Recomputation:", x=1.03, xanchor="left", xref="paper", y=0.40, yanchor="bottom", yref="paper", showarrow=False), + ], +) + +# Write fragment +fig.write_html("../../app/src/content/fragments/bar.html", + include_plotlyjs=False, + full_html=False, + config={ + 'displayModeBar': False, + 'responsive': True, + 'scrollZoom': False, + }) + diff --git a/app/src/content/embeds/original_embeds/plotly/heatmap.py b/app/src/content/embeds/original_embeds/plotly/heatmap.py new file mode 100644 index 0000000000000000000000000000000000000000..3ca90d9eb42502e593dad2545993e6195e9d1cff --- /dev/null +++ b/app/src/content/embeds/original_embeds/plotly/heatmap.py @@ -0,0 +1,125 @@ +import plotly.graph_objects as go +import plotly.io as pio +import numpy as np +import datetime as dt +import os + +""" +Calendar-like heatmap (GitHub-style) over the last 52 weeks. +Minimal, responsive, transparent background; suitable for Distill. +""" + +# Parameters +NUM_WEEKS = 52 +DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] + +# Build dates matrix (7 rows x NUM_WEEKS columns) +today = dt.date.today() +# Align to start of current week (Monday) +start = today - dt.timedelta(days=(today.weekday())) # Monday of current week +weeks = [start - dt.timedelta(weeks=w) for w in range(NUM_WEEKS-1, -1, -1)] +dates = [[weeks[c] + dt.timedelta(days=r) for c in range(NUM_WEEKS)] for r in range(7)] + +# Generate values (synthetic) — smooth seasonal pattern + noise +def gen_value(d: dt.date) -> float: + day_of_year = d.timetuple().tm_yday + base = 0.5 + 0.45 * np.sin(2 * np.pi * (day_of_year / 365.0)) + noise = np.random.default_rng(hash(d) % 2**32).uniform(-0.15, 0.15) + return max(0.0, min(1.0, base + noise)) + +z = [[gen_value(d) for d in row] for row in dates] +custom = [[d.isoformat() for d in row] for row in dates] + +# Colors aligned with other charts (slate / blue / gray) +colorscale = [ + [0.00, "#e5e7eb"], # light gray background for low + [0.40, "#64748b"], # slate-500 + [0.75, "#2563eb"], # blue-600 + [1.00, "#4b5563"], # gray-600 (high end accent) +] + +fig = go.Figure( + data=go.Heatmap( + z=z, + x=[w.isoformat() for w in weeks], + y=DAYS, + colorscale=colorscale, + showscale=False, + hovertemplate="Date: %{customdata}
Value: %{z:.2f}", + customdata=custom, + xgap=2, + ygap=2, + ) +) + +fig.update_layout( + autosize=True, + paper_bgcolor="rgba(0,0,0,0)", + plot_bgcolor="rgba(0,0,0,0)", + margin=dict(l=28, r=12, t=8, b=28), + xaxis=dict( + showgrid=False, + zeroline=False, + showline=False, + ticks="", + showticklabels=False, + fixedrange=True, + ), + yaxis=dict( + showgrid=False, + zeroline=False, + showline=False, + ticks="", + tickfont=dict(size=12, color="rgba(0,0,0,0.65)"), + fixedrange=True, + ), +) + +post_script = """ +(function(){ + var plots = document.querySelectorAll('.js-plotly-plot'); + plots.forEach(function(gd){ + function round(){ + try { + var root = gd && gd.parentNode ? gd.parentNode : document; + var rects = root.querySelectorAll('.hoverlayer .hovertext rect'); + rects.forEach(function(r){ r.setAttribute('rx', 8); r.setAttribute('ry', 8); }); + } catch(e) {} + } + if (gd && gd.on){ + gd.on('plotly_hover', round); + gd.on('plotly_unhover', round); + gd.on('plotly_relayout', round); + } + setTimeout(round, 0); + }); +})(); +""" + +html = pio.to_html( + fig, + include_plotlyjs=False, + full_html=False, + post_script=post_script, + config={ + "displayModeBar": False, + "responsive": True, + "scrollZoom": False, + "doubleClick": False, + "modeBarButtonsToRemove": [ + "zoom2d", "pan2d", "select2d", "lasso2d", + "zoomIn2d", "zoomOut2d", "autoScale2d", "resetScale2d", + "toggleSpikelines" + ], + }, +) + +fig.write_html("../app/src/content/fragments/heatmap.html", + include_plotlyjs=False, + full_html=False, + config={ + 'displayModeBar': False, + 'responsive': True, + 'scrollZoom': False, + }) + diff --git a/app/src/content/embeds/original_embeds/plotly/line.py b/app/src/content/embeds/original_embeds/plotly/line.py new file mode 100644 index 0000000000000000000000000000000000000000..1fc71f26a0f3385b8c5455c6668abb2d26333e0f --- /dev/null +++ b/app/src/content/embeds/original_embeds/plotly/line.py @@ -0,0 +1,276 @@ +import plotly.graph_objects as go +import plotly.io as pio +import numpy as np +import os +import uuid + +""" +Interactive line chart example (Baseline / Improved / Target) with a live slider. + +Context: research-style training curves for multiple datasets (CIFAR-10, CIFAR-100, ImageNet-1K). +The slider "Augmentation α" blends the Improved curve between the Baseline (α=0) +and an augmented counterpart (α=1) via a simple mixing equation. +Export remains responsive, with no zoom and no mode bar. +""" + +# Grid (x) and parameterization +N = 240 +x = np.linspace(0, 1, N) + +# Logistic helper for smooth learning curves +def logistic(xv: np.ndarray, ymin: float, ymax: float, k: float, x0: float) -> np.ndarray: + return ymin + (ymax - ymin) / (1.0 + np.exp(-k * (xv - x0))) + +# Plausible dataset params (baseline vs augmented) + a constant target line +datasets_params = [ + { + "name": "CIFAR-10", + "base": {"ymin": 0.10, "ymax": 0.90, "k": 10.0, "x0": 0.55}, + "aug": {"ymin": 0.15, "ymax": 0.96, "k": 12.0, "x0": 0.40}, + "target": 0.97, + }, + { + "name": "CIFAR-100", + "base": {"ymin": 0.05, "ymax": 0.70, "k": 9.5, "x0": 0.60}, + "aug": {"ymin": 0.08, "ymax": 0.80, "k": 11.0, "x0": 0.45}, + "target": 0.85, + }, + { + "name": "ImageNet-1K", + "base": {"ymin": 0.02, "ymax": 0.68, "k": 8.5, "x0": 0.65}, + "aug": {"ymin": 0.04, "ymax": 0.75, "k": 9.5, "x0": 0.50}, + "target": 0.82, + }, +] + +# Initial dataset index and alpha +alpha0 = 0.7 +ds0 = datasets_params[0] +base0 = logistic(x, **ds0["base"]) +aug0 = logistic(x, **ds0["aug"]) +target0 = np.full_like(x, ds0["target"], dtype=float) + +# Traces: Baseline (fixed), Improved (blended by α), Target (constant goal) +blend = lambda l, e, a: (1 - a) * l + a * e +y1 = base0 +y2 = blend(base0, aug0, alpha0) +y3 = target0 + +color_base = "#64748b" # slate-500 +color_improved = "#F981D4" # pink +color_target = "#4b5563" # gray-600 (dash) + +fig = go.Figure() +fig.add_trace( + go.Scatter( + x=x, + y=y1, + name="Baseline", + mode="lines", + line=dict(color=color_base, width=2, shape="spline", smoothing=0.6), + hovertemplate="%{fullData.name}
x=%{x:.2f}
y=%{y:.3f}", + showlegend=True, + ) +) +fig.add_trace( + go.Scatter( + x=x, + y=y2, + name="Improved", + mode="lines", + line=dict(color=color_improved, width=2, shape="spline", smoothing=0.6), + hovertemplate="%{fullData.name}
x=%{x:.2f}
y=%{y:.3f}", + showlegend=True, + ) +) +fig.add_trace( + go.Scatter( + x=x, + y=y3, + name="Target", + mode="lines", + line=dict(color=color_target, width=2, dash="dash"), + hovertemplate="%{fullData.name}
x=%{x:.2f}
y=%{y:.3f}", + showlegend=True, + ) +) + +fig.update_layout( + autosize=True, + paper_bgcolor="rgba(0,0,0,0)", + plot_bgcolor="rgba(0,0,0,0)", + margin=dict(l=40, r=28, t=20, b=40), + hovermode="x unified", + legend=dict( + orientation="v", + x=1, + y=0, + xanchor="right", + yanchor="bottom", + bgcolor="rgba(255,255,255,0)", + borderwidth=0, + ), + hoverlabel=dict( + bgcolor="white", + font=dict(color="#111827", size=12), + bordercolor="rgba(0,0,0,0.15)", + align="left", + namelength=-1, + ), + xaxis=dict( + showgrid=False, + zeroline=False, + showline=True, + linecolor="rgba(0,0,0,0.25)", + linewidth=1, + ticks="outside", + ticklen=6, + tickcolor="rgba(0,0,0,0.25)", + tickfont=dict(size=12, color="rgba(0,0,0,0.55)"), + title=None, + automargin=True, + fixedrange=True, + ), + yaxis=dict( + showgrid=False, + zeroline=False, + showline=True, + linecolor="rgba(0,0,0,0.25)", + linewidth=1, + ticks="outside", + ticklen=6, + tickcolor="rgba(0,0,0,0.25)", + tickfont=dict(size=12, color="rgba(0,0,0,0.55)"), + title=None, + tickformat=".2f", + rangemode="tozero", + automargin=True, + fixedrange=True, + ), +) + +# Write the fragment next to this file into src/fragments/line.html (robust path) +output_path = os.path.join(os.path.dirname(__file__), "fragments", "line.html") +os.makedirs(os.path.dirname(output_path), exist_ok=True) + +# Inject a small post-render script to round the hover box corners +post_script = """ +(function(){ + function attach(gd){ + function round(){ + try { + var root = gd && gd.parentNode ? gd.parentNode : document; + var rects = root.querySelectorAll('.hoverlayer .hovertext rect'); + rects.forEach(function(r){ r.setAttribute('rx', 8); r.setAttribute('ry', 8); }); + } catch(e) {} + } + if (gd && gd.on) { + gd.on('plotly_hover', round); + gd.on('plotly_unhover', round); + gd.on('plotly_relayout', round); + } + setTimeout(round, 0); + } + var plots = document.querySelectorAll('.js-plotly-plot'); + plots.forEach(attach); +})(); +""" + +html_plot = pio.to_html( + fig, + include_plotlyjs=False, + full_html=False, + post_script=post_script, + config={ + "displayModeBar": False, + "responsive": True, + "scrollZoom": False, + "doubleClick": False, + "modeBarButtonsToRemove": [ + "zoom2d", "pan2d", "select2d", "lasso2d", + "zoomIn2d", "zoomOut2d", "autoScale2d", "resetScale2d", + "toggleSpikelines" + ], + }, +) + +# Build a self-contained fragment with a live slider (no mouseup required) +uid = uuid.uuid4().hex[:8] +slider_id = f"line-ex-alpha-{uid}" +container_id = f"line-ex-container-{uid}" + +slider_tpl = ''' +
+ __PLOT__ +
+ + +
+
+ +''' + +slider_html = (slider_tpl + .replace('__CID__', container_id) + .replace('__SID__', slider_id) + .replace('__A0__', f"{alpha0:.2f}") + .replace('__N__', str(N)) + .replace('__PLOT__', html_plot) +) + +with open("../../app/src/content/fragments/line.html", "w", encoding="utf-8") as f: + f.write(slider_html) + diff --git a/app/src/content/embeds/original_embeds/plotly/poetry.lock b/app/src/content/embeds/original_embeds/plotly/poetry.lock new file mode 100644 index 0000000000000000000000000000000000000000..55dc5b270a044b51d1def9be28f25d940a66265f --- /dev/null +++ b/app/src/content/embeds/original_embeds/plotly/poetry.lock @@ -0,0 +1,511 @@ +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. + +[[package]] +name = "certifi" +version = "2025.8.3" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, + {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, + {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, +] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "markdown" +version = "3.8.2" +description = "Python implementation of John Gruber's Markdown." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24"}, + {file = "markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45"}, +] + +[package.extras] +docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "numpy" +version = "2.2.6" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version == \"3.10\"" +files = [ + {file = "numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb"}, + {file = "numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"}, + {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163"}, + {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf"}, + {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83"}, + {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915"}, + {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680"}, + {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289"}, + {file = "numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d"}, + {file = "numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491"}, + {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a"}, + {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf"}, + {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1"}, + {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab"}, + {file = "numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47"}, + {file = "numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282"}, + {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87"}, + {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249"}, + {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49"}, + {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de"}, + {file = "numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4"}, + {file = "numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566"}, + {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f"}, + {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f"}, + {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868"}, + {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d"}, + {file = "numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd"}, + {file = "numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8"}, + {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f"}, + {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa"}, + {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571"}, + {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1"}, + {file = "numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff"}, + {file = "numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00"}, + {file = "numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd"}, +] + +[[package]] +name = "numpy" +version = "2.3.2" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.11" +groups = ["main"] +markers = "python_version >= \"3.11\"" +files = [ + {file = "numpy-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:852ae5bed3478b92f093e30f785c98e0cb62fa0a939ed057c31716e18a7a22b9"}, + {file = "numpy-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a0e27186e781a69959d0230dd9909b5e26024f8da10683bd6344baea1885168"}, + {file = "numpy-2.3.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f0a1a8476ad77a228e41619af2fa9505cf69df928e9aaa165746584ea17fed2b"}, + {file = "numpy-2.3.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cbc95b3813920145032412f7e33d12080f11dc776262df1712e1638207dde9e8"}, + {file = "numpy-2.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75018be4980a7324edc5930fe39aa391d5734531b1926968605416ff58c332d"}, + {file = "numpy-2.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b8200721840f5621b7bd03f8dcd78de33ec522fc40dc2641aa09537df010c3"}, + {file = "numpy-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f91e5c028504660d606340a084db4b216567ded1056ea2b4be4f9d10b67197f"}, + {file = "numpy-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097"}, + {file = "numpy-2.3.2-cp311-cp311-win32.whl", hash = "sha256:4ae6863868aaee2f57503c7a5052b3a2807cf7a3914475e637a0ecd366ced220"}, + {file = "numpy-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:240259d6564f1c65424bcd10f435145a7644a65a6811cfc3201c4a429ba79170"}, + {file = "numpy-2.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:4209f874d45f921bde2cff1ffcd8a3695f545ad2ffbef6d3d3c6768162efab89"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b"}, + {file = "numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370"}, + {file = "numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73"}, + {file = "numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc"}, + {file = "numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be"}, + {file = "numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036"}, + {file = "numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f"}, + {file = "numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089"}, + {file = "numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2"}, + {file = "numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f"}, + {file = "numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee"}, + {file = "numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6"}, + {file = "numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b"}, + {file = "numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56"}, + {file = "numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286"}, + {file = "numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8"}, + {file = "numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a"}, + {file = "numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91"}, + {file = "numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5"}, + {file = "numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5"}, + {file = "numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450"}, + {file = "numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19"}, + {file = "numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f"}, + {file = "numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5"}, + {file = "numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58"}, + {file = "numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0"}, + {file = "numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2"}, + {file = "numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b"}, + {file = "numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2"}, + {file = "numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0"}, + {file = "numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0"}, + {file = "numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2"}, + {file = "numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf"}, + {file = "numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1"}, + {file = "numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b"}, + {file = "numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:14a91ebac98813a49bc6aa1a0dfc09513dcec1d97eaf31ca21a87221a1cdcb15"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:71669b5daae692189540cffc4c439468d35a3f84f0c88b078ecd94337f6cb0ec"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:69779198d9caee6e547adb933941ed7520f896fd9656834c300bdf4dd8642712"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2c3271cc4097beb5a60f010bcc1cc204b300bb3eafb4399376418a83a1c6373c"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446acd11fe3dc1830568c941d44449fd5cb83068e5c70bd5a470d323d448296"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa098a5ab53fa407fded5870865c6275a5cd4101cfdef8d6fafc48286a96e981"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619"}, + {file = "numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48"}, +] + +[[package]] +name = "packaging" +version = "25.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, +] + +[[package]] +name = "pandas" +version = "2.3.2" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pandas-2.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52bc29a946304c360561974c6542d1dd628ddafa69134a7131fdfd6a5d7a1a35"}, + {file = "pandas-2.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:220cc5c35ffaa764dd5bb17cf42df283b5cb7fdf49e10a7b053a06c9cb48ee2b"}, + {file = "pandas-2.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c05e15111221384019897df20c6fe893b2f697d03c811ee67ec9e0bb5a3424"}, + {file = "pandas-2.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc03acc273c5515ab69f898df99d9d4f12c4d70dbfc24c3acc6203751d0804cf"}, + {file = "pandas-2.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d25c20a03e8870f6339bcf67281b946bd20b86f1a544ebbebb87e66a8d642cba"}, + {file = "pandas-2.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21bb612d148bb5860b7eb2c10faacf1a810799245afd342cf297d7551513fbb6"}, + {file = "pandas-2.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:b62d586eb25cb8cb70a5746a378fc3194cb7f11ea77170d59f889f5dfe3cec7a"}, + {file = "pandas-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1333e9c299adcbb68ee89a9bb568fc3f20f9cbb419f1dd5225071e6cddb2a743"}, + {file = "pandas-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76972bcbd7de8e91ad5f0ca884a9f2c477a2125354af624e022c49e5bd0dfff4"}, + {file = "pandas-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b98bdd7c456a05eef7cd21fd6b29e3ca243591fe531c62be94a2cc987efb5ac2"}, + {file = "pandas-2.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d81573b3f7db40d020983f78721e9bfc425f411e616ef019a10ebf597aedb2e"}, + {file = "pandas-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e190b738675a73b581736cc8ec71ae113d6c3768d0bd18bffa5b9a0927b0b6ea"}, + {file = "pandas-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c253828cb08f47488d60f43c5fc95114c771bbfff085da54bfc79cb4f9e3a372"}, + {file = "pandas-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:9467697b8083f9667b212633ad6aa4ab32436dcbaf4cd57325debb0ddef2012f"}, + {file = "pandas-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fbb977f802156e7a3f829e9d1d5398f6192375a3e2d1a9ee0803e35fe70a2b9"}, + {file = "pandas-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b9b52693123dd234b7c985c68b709b0b009f4521000d0525f2b95c22f15944b"}, + {file = "pandas-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bd281310d4f412733f319a5bc552f86d62cddc5f51d2e392c8787335c994175"}, + {file = "pandas-2.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96d31a6b4354e3b9b8a2c848af75d31da390657e3ac6f30c05c82068b9ed79b9"}, + {file = "pandas-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:df4df0b9d02bb873a106971bb85d448378ef14b86ba96f035f50bbd3688456b4"}, + {file = "pandas-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:213a5adf93d020b74327cb2c1b842884dbdd37f895f42dcc2f09d451d949f811"}, + {file = "pandas-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c13b81a9347eb8c7548f53fd9a4f08d4dfe996836543f805c987bafa03317ae"}, + {file = "pandas-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c6ecbac99a354a051ef21c5307601093cb9e0f4b1855984a084bfec9302699e"}, + {file = "pandas-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c6f048aa0fd080d6a06cc7e7537c09b53be6642d330ac6f54a600c3ace857ee9"}, + {file = "pandas-2.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0064187b80a5be6f2f9c9d6bdde29372468751dfa89f4211a3c5871854cfbf7a"}, + {file = "pandas-2.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac8c320bded4718b298281339c1a50fb00a6ba78cb2a63521c39bec95b0209b"}, + {file = "pandas-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:114c2fe4f4328cf98ce5716d1532f3ab79c5919f95a9cfee81d9140064a2e4d6"}, + {file = "pandas-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:48fa91c4dfb3b2b9bfdb5c24cd3567575f4e13f9636810462ffed8925352be5a"}, + {file = "pandas-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:12d039facec710f7ba305786837d0225a3444af7bbd9c15c32ca2d40d157ed8b"}, + {file = "pandas-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c624b615ce97864eb588779ed4046186f967374185c047070545253a52ab2d57"}, + {file = "pandas-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0cee69d583b9b128823d9514171cabb6861e09409af805b54459bd0c821a35c2"}, + {file = "pandas-2.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2319656ed81124982900b4c37f0e0c58c015af9a7bbc62342ba5ad07ace82ba9"}, + {file = "pandas-2.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b37205ad6f00d52f16b6d09f406434ba928c1a1966e2771006a9033c736d30d2"}, + {file = "pandas-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:837248b4fc3a9b83b9c6214699a13f069dc13510a6a6d7f9ba33145d2841a012"}, + {file = "pandas-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d2c3554bd31b731cd6490d94a28f3abb8dd770634a9e06eb6d2911b9827db370"}, + {file = "pandas-2.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:88080a0ff8a55eac9c84e3ff3c7665b3b5476c6fbc484775ca1910ce1c3e0b87"}, + {file = "pandas-2.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d4a558c7620340a0931828d8065688b3cc5b4c8eb674bcaf33d18ff4a6870b4a"}, + {file = "pandas-2.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45178cf09d1858a1509dc73ec261bf5b25a625a389b65be2e47b559905f0ab6a"}, + {file = "pandas-2.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77cefe00e1b210f9c76c697fedd8fdb8d3dd86563e9c8adc9fa72b90f5e9e4c2"}, + {file = "pandas-2.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:13bd629c653856f00c53dc495191baa59bcafbbf54860a46ecc50d3a88421a96"}, + {file = "pandas-2.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:36d627906fd44b5fd63c943264e11e96e923f8de77d6016dc2f667b9ad193438"}, + {file = "pandas-2.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:a9d7ec92d71a420185dec44909c32e9a362248c4ae2238234b76d5be37f208cc"}, + {file = "pandas-2.3.2.tar.gz", hash = "sha256:ab7b58f8f82706890924ccdfb5f48002b83d2b5a3845976a9fb705d36c34dcdb"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] + +[[package]] +name = "plotly" +version = "5.24.1" +description = "An open-source, interactive data visualization library for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089"}, + {file = "plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae"}, +] + +[package.dependencies] +packaging = "*" +tenacity = ">=6.2.0" + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2025.2" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, + {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, +] + +[[package]] +name = "requests" +version = "2.32.5" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, + {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset_normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "tenacity" +version = "9.1.2" +description = "Retry code until it succeeds" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, + {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, +] + +[package.extras] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] + +[[package]] +name = "tzdata" +version = "2025.2" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +groups = ["main"] +files = [ + {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, + {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[metadata] +lock-version = "2.1" +python-versions = ">=3.10,<3.13" +content-hash = "a7f64c43efcba78952701498a72a8fe503e995841717b2d5de4c9aa20c9a996a" diff --git a/app/src/content/embeds/original_embeds/plotly/pyproject.toml b/app/src/content/embeds/original_embeds/plotly/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..2c020f06dbed2c5d70596f65de09d943de4b100b --- /dev/null +++ b/app/src/content/embeds/original_embeds/plotly/pyproject.toml @@ -0,0 +1,20 @@ +[tool.poetry] +name = "blogpost-fine-tasks-python" +version = "0.1.0" +description = "Plotly fragment generation scripts and HTML/Markdown conversions for the blogpost." +package-mode = false + +[tool.poetry.dependencies] +python = ">=3.10,<3.13" +Markdown = "^3.6" +requests = "^2.32.3" +numpy = "^2.0.0" +pandas = "^2.2.2" +plotly = "^5.24.0" + +[tool.poetry.scripts] +html-to-md = "convert_to_md:main" + +[build-system] +requires = ["poetry-core>=1.5.0"] +build-backend = "poetry.core.masonry.api" diff --git a/app/src/content/embeds/plotly-line.html b/app/src/content/embeds/plotly-line.html new file mode 100644 index 0000000000000000000000000000000000000000..91056f958235aea35d9731272a508f8eed2e7add --- /dev/null +++ b/app/src/content/embeds/plotly-line.html @@ -0,0 +1,84 @@ + +
+
+
+ + +
+
+ diff --git a/app/src/content/embeds/throughput-debug-1node.html b/app/src/content/embeds/throughput-debug-1node.html new file mode 100644 index 0000000000000000000000000000000000000000..c4c4bc8be805b4a17adaed28a894e153186e9f5d --- /dev/null +++ b/app/src/content/embeds/throughput-debug-1node.html @@ -0,0 +1,352 @@ +
+ + diff --git a/app/src/content/embeds/throughput-drops-comparison.html b/app/src/content/embeds/throughput-drops-comparison.html new file mode 100644 index 0000000000000000000000000000000000000000..96a4dd5037599471e622812f1bc3ce51d5f03f1d --- /dev/null +++ b/app/src/content/embeds/throughput-drops-comparison.html @@ -0,0 +1,335 @@ +
+ + diff --git a/app/src/content/embeds/throughput-weka-drops.html b/app/src/content/embeds/throughput-weka-drops.html new file mode 100644 index 0000000000000000000000000000000000000000..b66603fd84951ba6d45ffb84cb5c06c1f2fce947 --- /dev/null +++ b/app/src/content/embeds/throughput-weka-drops.html @@ -0,0 +1,283 @@ +
+ + diff --git a/app/src/content/embeds/vibe-code-d3-embeds-directives.md b/app/src/content/embeds/vibe-code-d3-embeds-directives.md new file mode 100644 index 0000000000000000000000000000000000000000..329833c8a753e2ee29aff29f8edd12da5d4481c8 --- /dev/null +++ b/app/src/content/embeds/vibe-code-d3-embeds-directives.md @@ -0,0 +1,504 @@ +## Embed Chart Authoring Guidelines + +### Quickstart (TL;DR) +- Create a single self-contained HTML fragment: root div + scoped style + IIFE script. +- Draw marks/axes in SVG; render UI (legend and controls) in HTML. +- Place legend and controls BELOW the chart (header appended after the chart). Include a legend title "Legend" and a select labeled "Metric" when relevant. +- Load data from public `/data` first, then fall back to `assets/data`. +- Use `window.ColorPalettes` for colors; stick to CSS variables for theming. + +Minimal header markup: +```html +
+
Legend
+
+ +
+
+
+ + +
+ +
+``` + +See also: `d3-line-simple.html`, `d3-line-quad.html`, `d3-benchmark.html`. + +Authoring rules for creating a new interactive chart as a single self-contained `.html` file under `src/content/embeds/`. These conventions are derived from `d3-bar.html`, `d3-comparison.html`, `d3-neural.html`, `d3-line.html`, and `d3-pie.html`. + +### A) Colors & palettes (MANDATORY) +- Always obtain color arrays from `window.ColorPalettes`; do not hardcode palettes. +- Use the categorical/sequential/diverging helpers and the current primary color. +- If you change `--primary-color` dynamically, call `window.ColorPalettes.refresh()` so listeners update. + +Usage: +```js +// Usage (with explicit counts) +const cat = window.ColorPalettes.getColors('categorical', 8); +const seq = window.ColorPalettes.getColors('sequential', 8); +const div = window.ColorPalettes.getColors('diverging', 7); + +// For current primary color string +const primaryHex = window.ColorPalettes.getPrimary(); + +// If you change --primary-color dynamically, call refresh to notify listeners +document.documentElement.style.setProperty('--primary-color', '#6D4AFF'); +window.ColorPalettes.refresh(); +``` + +Notes: +- Keep chart accents (lines, markers, selection) aligned with `--primary-color`. +- Prefer CSS variables for fills/strokes when possible; derive series colors via `ColorPalettes`. +- Provide a graceful fallback to CSS variables if `window.ColorPalettes` is unavailable. + +### B) Layout & form elements (HTML-only) +- All UI controls (labels, selects, sliders, buttons, toggles) must be plain HTML inside the root container. +- Do not draw controls with SVG; style them consistently (rounded 8px, custom caret, focus ring). +- Use `