Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions .github/workflows/notebook_to_html.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
# This is a basic workflow to help you get started with Actions

name: Jupyter Book
name: MyST Book

# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the "main" branch
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:



# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
Expand All @@ -22,7 +21,7 @@ jobs:

# To prevent github actions from eating up too many enterprise minutes
timeout-minutes: 5

# https://github.com/actions/starter-workflows/blob/55eb18560f57898549b12afa6defe7cc79705d6a/pages/static.yml#L13
permissions:
contents: read
Expand All @@ -34,13 +33,13 @@ jobs:
# Checks out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v4

# Convert unstable/*.md → unstable/*.ipynb so the site provides "Download notebook" buttons
# Convert basic_lessons/*.md → basic_lessons/*.ipynb so the site provides "Download notebook" buttons
# https://jupytext.readthedocs.io/ — supports md:myst format natively
# Must run BEFORE build so myst/jupyter-book can pick up the generated notebooks
- name: Generate downloadable notebooks
run: |
pip install jupytext
for f in unstable/lesson*_tutorial.md unstable/lesson*_exercise_answers.md; do
for f in basic_lessons/lesson*_tutorial.md basic_lessons/lesson*_exercise_answers.md; do
[ -f "$f" ] || continue
out="${f%.md}.ipynb"
python -m jupytext --from md:myst --to notebook --output "$out" "$f"
Expand Down
183 changes: 73 additions & 110 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,116 +2,82 @@

## Overview

**Open Executable Books in Robotics** is a collection of Jupyter notebooks teaching kinematic modelling and control of serial-link robotic manipulators. The project is licensed under [CC-BY-NC-SA 4.0](LICENSE) and hosted at <https://github.com/MarinhoLab/OpenExecutableBooksRobotics>.
**Open Executable Books in Robotics** is a collection of MyST text notebooks teaching kinematic modelling and control of serial-link robotic manipulators. The project is licensed under [CC-BY-NC-SA 4.0](LICENSE) and hosted at <https://github.com/MarinhoLab/OpenExecutableBooksRobotics>.

---

## Repository Structure

| Path | Purpose |
|------|---------|
| `basic_lessons/` | Canonical source: `.ipynb` notebooks (5 tutorials + 5 exercise answer keys) |
| `unstable/` | Work-in-progress text-only MyST notebooks (`.md` with `{code-cell}` directives) |
| `basic_lessons/` | Canonical source: MyST text notebooks (`.md` with `{code-cell}` directives) — 6 tutorials + 5 exercise answer keys |
| `basic_lessons/.gitignore` | Excludes generated `.ipynb` files (produced at build time) |
| `other/` | Supplementary content (e.g. `dqrobotics.md`) |
| `convert_to_myst.py` | Script: converts `basic_lessons/*.ipynb` → `unstable/*.md` |
| `myst.yml` | MyST project config (root): LaTeX macros, TOC (including unstable section), site options |
| `unstable/myst.yml` | Standalone MyST project config for unstable-only builds (optional) |
| `build_html.sh` | Build script for `jupyter-book` (legacy pipeline) |
| `myst.yml` | MyST project config (root): LaTeX macros, TOC, site options |
| `build_html.sh` | Build script for `jupyter-book` |
| `conf.py` | MyST parser extensions (`dollarmath`) |
| `_build/` | Build artifacts (excluded from git via `unstable/.gitignore`) |
| `_build/` | Build artifacts (excluded from git) |

---

## Modifying `.ipynb` Files
## Modifying MyST Text Notebooks

Jupyter notebooks are JSON files. Every cell's `source` field is a **list of strings**, where **each string must end with `\n`** (trailing newline). This is critical:
Lessons are [MyST text notebooks](https://mystmd.org/guide/notebooks-with-markdown) — plain Markdown files with `{code-cell}` directives. They are version-control friendly and human-readable.

### Correct format (renders properly in Jupyter):
```json
"source": [
"# L1 A quick Python refresher\n",
"\n",
"*License: CC-BY-NC-SA 4.0*\n",
"\n",
"### Prerequisites\n",
"The user of this notebook is expected to have prior knowledge in\n"
]
```

### Broken format (renders as one concatenated line):
```json
"source": [
"# L1 A quick Python refresher",
"",
"*License: CC-BY-NC-SA 4.0*",
""
]
```
### Structure

### When editing notebooks programmatically:
1. Load with `json.load()`, modify `cell['source']` entries.
2. **Every source line must end with `\n`** before writing back.
3. Save with `json.dump(nb, f, indent=1)` (single-space indent is standard).
4. Clear execution state on code cells to avoid stale output:
```python
cell['outputs'] = []
cell['execution_count'] = None
```

### When editing notebooks manually:
- Use a notebook editor (Jupyter, VSCode, or nbconvert) rather than raw text edits.
- If editing raw JSON, always verify trailing `\n` on source lines.

### Cell types:
| Type | Purpose |
|------|---------|
| `markdown` | Text, equations, images, headings |
| `code` | Python cells (numpy, math) |
| `raw` | Raw LaTeX macro definitions (`\providecommand`) |

### LaTeX macros:
Custom macros (`\myvec`, `\mymatrix`, `\quat`, `\dual`) are defined in two places:
1. As **raw cells** in each notebook (for Jupyter/LaTeX rendering)
2. In **`myst.yml`** under `project.math` (for MyST rendering)
Each `.md` lesson file begins with YAML frontmatter declaring the kernel:

```yaml
---
kernelspec:
name: python3
display_name: 'Python 3'
---
```

## Converting to MyST Text Notebooks
Code cells are delimited with `{code-cell}` directives:

Run the converter script to regenerate `unstable/*.md` from the canonical notebooks:
````markdown
````{code-cell}
import numpy as np
x = np.array([1, 2, 3])
````
````

```bash
python3 convert_to_myst.py
```
### Editing guidelines

This script:
- Copies images (`Lesson4.png`, `Lesson4.svg`) to `unstable/`
- Converts markdown cells as-is, code cells as ` ````{code-cell}```` directives
- Strips raw cells and LaTeX macro markdown cells (handled by `myst.yml`)
- Fixes `attachment:` image syntax → plain relative paths
- Handles both trailing-newline and no-trailing-newline source formats
- Edit `.md` files directly — they are plain text.
- Every lesson should follow the header format convention (see below).
- Keep code cells focused and self-contained.
- LaTeX equations use inline `$...$` or display `$$...$$` syntax with the `dollarmath` MyST extension enabled in `conf.py`.
- Custom macros (`\myvec`, `\mymatrix`, `\quat`, `\dual`) are defined in `myst.yml` under `project.math`.

### MyST Compatibility Notes
### `%%capture` magic

- **`%%capture` magic IS supported** in MyST text notebooks. MyST uses a Jupyter Server with an IPython kernel to execute code cells ([Execute Notebooks at Build Time](https://mystmd.org/guide/execute-notebooks)). The `%%capture` magic is a built-in IPython cell magic ([Built-in magic commands — IPython](https://ipython.readthedocs.io/en/stable/interactive/magics.html)) and works correctly during MyST execution.
- If output suppression is needed without `%%capture`, the MyST-native approach is to use cell tags like `remove-stdout` and `remove-stderr` on the `{code-cell}` directive.
**`%%capture` magic IS supported** in MyST text notebooks. MyST uses a Jupyter Server with an IPython kernel to execute code cells ([Execute Notebooks at Build Time](https://mystmd.org/guide/execute-notebooks)). The `%%capture` magic is a built-in IPython cell magic ([Built-in magic commands — IPython](https://ipython.readthedocs.io/en/stable/interactive/magics.html)) and works correctly during MyST execution. Use `%%capture` on `%pip install` cells to suppress output.

### Downloadable `.ipynb` from `.md` notebooks

The `unstable/` `.md` files are the canonical source. `.ipynb` files are generated at build time so visitors can download them:
The `basic_lessons/` `.md` files are the canonical source. `.ipynb` files are generated at build time so visitors can download them:

1. **CI pipeline** (`.github/workflows/notebook_to_html.yml`) runs `jupytext --from md:myst --to notebook` before the MyST build, converting each `unstable/*.md` → `unstable/*.ipynb`.
2. **`myst.yml` TOC** references the generated `.ipynb` for the unstable section — MyST renders these identically to the `.md` but provides native "Download notebook" buttons.
3. **`unstable/.gitignore`** excludes `*.ipynb` so only `.md` is tracked in git.
1. **CI pipeline** (`.github/workflows/notebook_to_html.yml`) runs `jupytext --from md:myst --to notebook` before the MyST build, converting each `basic_lessons/*.md` → `basic_lessons/*.ipynb`.
2. **`myst.yml` TOC** references the generated `.ipynb` for the lesson section — MyST renders these identically to the `.md` but provides native "Download notebook" buttons.
3. **`basic_lessons/.gitignore`** excludes `.ipynb` so only `.md` is tracked in git.

To generate locally (e.g. for testing):
```bash
pip install jupytext
for f in unstable/lesson*_tutorial.md unstable/lesson*_exercise_answers.md; do
for f in basic_lessons/lesson*_tutorial.md basic_lessons/lesson*_exercise_answers.md; do
python -m jupytext --from md:myst --to notebook --output "${f%.md}.ipynb" "$f"
done
```

### Image references

- Use relative paths: `![alt](Lesson4.png)` (relative to `basic_lessons/`)
- Images (`Lesson4.png`, `Lesson4.svg`) live alongside the lesson files in `basic_lessons/`.

---

## Building & Testing
Expand All @@ -132,41 +98,38 @@ pip install jupyter-book --pre

### Build Commands

**MyST build (root — includes all lessons + unstable):**
**jupyter-book build (CI pipeline):**
```bash
# Step 1: Generate .ipynb from .md (required for unstable section)
# Step 1: Generate .ipynb from .md (required for download buttons)
pip install jupytext
for f in unstable/lesson*_tutorial.md unstable/lesson*_exercise_answers.md; do
for f in basic_lessons/lesson*_tutorial.md basic_lessons/lesson*_exercise_answers.md; do
python -m jupytext --from md:myst --to notebook --output "${f%.md}.ipynb" "$f"
done

# Step 2: Build the site
myst build --html
```
- `--execute` runs all code cells and caches results in `_build/execute/`
- `--html` produces HTML output in `_build/html/`
- Site format (JSON) goes to `_build/site/`

**MyST build (unstable only — optional):**
```bash
cd unstable
myst build --execute --html
```

**Legacy jupyter-book build (root):**
```bash
chmod +x build_html.sh
./build_html.sh
```
- Requires `BASE_URL` env variable for correct link resolution
- Installs `jupyter-book --pre` (Jupyter Book 2.0 alpha)
- Sets `BASE_URL` for correct link resolution
- Runs `python -m jupyter book build --html --execute`
- Outputs to `_build/html/`

**MyST build (local development):**
```bash
pip install mystmd jupyter-server ipykernel
myst build --html
```
- `--execute` runs all code cells and caches results in `_build/execute/`
- `--html` produces HTML output in `_build/html/`

### CI/CD

The GitHub Actions workflow (`.github/workflows/notebook_to_html.yml`) runs on pushes/PRs to `main`:
1. Runs `./build_html.sh` (jupyter-book pipeline)
2. Uploads `_build/html/` as Pages artifact
3. Deploys to GitHub Pages
The GitHub Actions workflow (`.github/workflows/notebook_to_html.yml`) runs on pushes to `main` and on pull requests:
1. Generates `.ipynb` from `.md` using jupytext
2. Runs `./build_html.sh` (jupyter-book pipeline)
3. Uploads `_build/html/` as Pages artifact
4. Deploys to GitHub Pages

**Timeout:** 5 minutes. Keep cells fast to avoid CI failures.

Expand Down Expand Up @@ -196,9 +159,8 @@ Thank you! Please report it at https://github.com/MarinhoLab/OpenExecutableBooks
- `\dual{}` for dual numbers

### Image references:
- In `.ipynb`: `![alt](Lesson4.png)` (relative to `basic_lessons/`)
- In `.md` (unstable): same — images are copied to `unstable/`
- Avoid `attachment:` prefix in MyST notebooks
- Use relative paths: `![alt](Lesson4.png)` (relative to `basic_lessons/`)
- Images live alongside the lesson files in `basic_lessons/`.

### Language:
- **UK English** spelling (e.g. *behaviour*, *modelling*, *summarised*)
Expand All @@ -215,18 +177,19 @@ Thank you! Please report it at https://github.com/MarinhoLab/OpenExecutableBooks

### Files excluded from version control:
- `venv/` — Python virtual environment
- `_build/` — Build artifacts (both root and `unstable/`)
- `unstable/.gitignore` already excludes `unstable/_build/`
- `_build/` — Build artifacts
- `basic_lessons/*.ipynb` — Generated at build time from `.md` files

---

## Adding a New Lesson

1. Create `basic_lessons/lesson<N>_tutorial.ipynb` and `basic_lessons/lesson<N>_exercise_answers.ipynb`
2. Follow the header format convention above
3. Add LaTeX macro raw cell (or markdown cell with `vscode` language metadata)
4. Update `myst.yml` → add new file to `project.toc` list
5. Run `python3 convert_to_myst.py` to regenerate `unstable/`
6. Update `myst.yml` → add new unstable `.md` file to the "Unstable" section in `project.toc`
7. Test: `myst build --html` from the repository root
8. Open PR with descriptive title and body
1. Create `basic_lessons/lesson<N>_tutorial.md` (and optionally `basic_lessons/lesson<N>_exercise_answers.md`)
2. Add YAML frontmatter with kernelspec at the top of the file
3. Follow the header format convention above
4. Use `{code-cell}` directives for Python code blocks
5. Use `%%capture` on `%pip install` cells to suppress output
6. Update `myst.yml` — add new file(s) to `project.toc` list as `.ipynb` (generated at build time)
7. Update `basic_lessons/README.md` — add the new lesson to the contents table
8. Test: `./build_html.sh` from the repository root
9. Open PR with descriptive title and body
2 changes: 2 additions & 0 deletions basic_lessons/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Generated .ipynb from .md (for downloadable notebooks)
*.ipynb
26 changes: 19 additions & 7 deletions basic_lessons/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# The Basics of Kinematic Modelling and Control of Serial-link Manipulators Using `numpy`

In this five-lesson tutorial, we start from the very basics of scalar and matricial operations in Python using `numpy`,
In this six-lesson tutorial, we start from the very basics of setting up your Python environment,
then cover scalar and matricial operations in Python using `numpy`,
all the way until the basics of kinematic control. Until kinematic control, most is based on [@spong2020robot].

# Using this book

Each lesson is a [Jupyter notebook](https://jupyter-notebook.readthedocs.io/en/stable/notebook.html). Each lesson can be
Each lesson is a [MyST text notebook](https://mystmd.org/guide/notebooks-with-markdown). Each lesson can be
opened and executed with popular IDEs, such as [VSCode](https://code.visualstudio.com) and [PyCharm](https://www.jetbrains.com/pycharm/).
The reader is expected to follow it sequentially.

Expand All @@ -14,8 +15,19 @@ The reader is expected to follow it sequentially.

| Number | Title and Link | Content |
|--------|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 | [](./lesson1_tutorial.ipynb) | Basic operations in Python and `numpy` |
| 2 | [](./lesson2_tutorial.ipynb) | Learn about elements and operations in $\mathbb{R}^n$, $SO(n)$, and $SE(n)$ with $n\in{\{2,3\}}$ related to positions, orientations, and poses, respectively. |
| 3 | [](./lesson3_tutorial.ipynb) | Learn about the composition of rigid body motion in series to obtain the forward kinematics model of a robotic manipulator, mapping their configuration space $\myvec{q}\in\mathbb{R}^n$ into their task space $\myvec{x}\in\mathbb{R}^m$. |
| 4 | [](./lesson4_tutorial.ipynb) | Learn about the first-order differential mapping $\dot{\myvec{x}}=\mymatrix{J}\dot{\myvec{q}}$ between joint space and task space velocities through the calculation of the Jacobian $\mymatrix{J}$. |
| 5 | [](./lesson5_tutorial.ipynb) | Employ the previous knowledge in all previous lessons to employ a Lyapunov-stable control law to move a manipulator in task space using configuration-space signals. |
| 0 | [](./lesson0_tutorial.md) | Setting up the virtual environment and installing all required dependencies. |
| 1 | [](./lesson1_tutorial.md) | Basic operations in Python and `numpy` |
| 2 | [](./lesson2_tutorial.md) | Learn about elements and operations in $\mathbb{R}^n$, $SO(n)$, and $SE(n)$ with $n\in{\{2,3\}}$ related to positions, orientations, and poses, respectively. |
| 3 | [](./lesson3_tutorial.md) | Learn about the composition of rigid body motion in series to obtain the forward kinematics model of a robotic manipulator, mapping their configuration space $\myvec{q}\in\mathbb{R}^n$ into their task space $\myvec{x}\in\mathbb{R}^m$. |
| 4 | [](./lesson4_tutorial.md) | Learn about the first-order differential mapping $\dot{\myvec{x}}=\mymatrix{J}\dot{\myvec{q}}$ between joint space and task space velocities through the calculation of the Jacobian $\mymatrix{J}$. |
| 5 | [](./lesson5_tutorial.md) | Employ the previous knowledge in all previous lessons to employ a Lyapunov-stable control law to move a manipulator in task space using configuration-space signals. |

### Exercise Answers

| Lesson | Link |
|--------|------|
| L1 | [](./lesson1_exercise_answers.md) |
| L2 | [](./lesson2_exercise_answers.md) |
| L3 | [](./lesson3_exercise_answers.md) |
| L4 | [](./lesson4_exercise_answers.md) |
| L5 | [](./lesson5_exercise_answers.md) |
File renamed without changes.
Loading
Loading