Skip to content

Commit 58ee2ab

Browse files
committed
feat: add hub with login.
1 parent 65a1449 commit 58ee2ab

53 files changed

Lines changed: 3147 additions & 907 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "repl"]
2+
path = repl
3+
url = https://github.com/leanprover-community/repl

Dockerfile

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ COPY . /opt/build
1313

1414
RUN mkdir -p /opt/bin \
1515
&& cabal build exe:sabela \
16-
&& cp "$(cabal list-bin sabela)" /opt/bin/sabela
16+
&& cp "$(cabal list-bin sabela)" /opt/bin/sabela \
17+
&& strip /opt/bin/sabela
1718

1819
# Pre-install dataframe package in build stage so we can copy the store
1920
RUN cabal install dataframe
@@ -24,26 +25,10 @@ FROM haskell:9.12.2-bookworm
2425
RUN apt-get update && apt-get install -y --no-install-recommends \
2526
python3 \
2627
python3-venv \
27-
tini \
28+
curl \
2829
&& rm -rf /var/lib/apt/lists/*
2930

30-
31-
# Install Python ML packages in a venv (no cache, no .pyc)
32-
ENV VIRTUAL_ENV="/opt/venv"
33-
RUN python3 -m venv $VIRTUAL_ENV
34-
ENV PATH="$VIRTUAL_ENV/bin:${PATH}"
35-
RUN pip install --no-cache-dir --no-compile numpy pandas scikit-learn matplotlib \
36-
&& find $VIRTUAL_ENV -name '__pycache__' -exec rm -rf {} + 2>/dev/null || true
37-
38-
# Install elan (Lean 4 toolchain manager)
39-
ENV ELAN_HOME="/root/.elan"
40-
ENV PATH="${ELAN_HOME}/bin:${PATH}"
41-
RUN curl -sSf https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh \
42-
| sh -s -- -y --default-toolchain leanprover/lean4:v4.29.0
43-
44-
# Verify installations
45-
RUN python3 --version && lean --version && lake --version
46-
31+
# ---------- Assemble final image ----------
4732
WORKDIR /opt/sabela
4833

4934
# Copy compiled binary
@@ -61,6 +46,27 @@ COPY ./examples /opt/sabela/examples/
6146

6247
ENV CABAL_DIR="/root/.cabal"
6348

64-
ENTRYPOINT ["/usr/bin/tini", "--"]
65-
49+
# Entrypoint script prepends EFS tool paths to PATH at runtime
50+
# (avoids hardcoding PATH in task definition, which broke GHC discovery)
51+
COPY <<'SCRIPT' /opt/bin/start.sh
52+
#!/bin/sh
53+
# Add EFS-mounted tools to PATH if they exist
54+
[ -d "/mnt/sabela/python/venv/bin" ] && export PATH="/mnt/sabela/python/venv/bin:$PATH"
55+
[ -d "/mnt/sabela/lean/.elan/bin" ] && export PATH="/mnt/sabela/lean/.elan/bin:$PATH"
56+
57+
# If a user work directory is specified (3rd arg = sabela's 2nd arg), set it up
58+
WORKDIR="${3:-.}"
59+
if echo "$WORKDIR" | grep -q "^/mnt/sabela/users/"; then
60+
mkdir -p "$WORKDIR"
61+
# Copy examples into user dir if not already there
62+
if [ ! -d "$WORKDIR/examples" ]; then
63+
cp -r /opt/sabela/examples "$WORKDIR/examples"
64+
fi
65+
fi
66+
67+
exec "$@"
68+
SCRIPT
69+
RUN chmod +x /opt/bin/start.sh
70+
71+
ENTRYPOINT ["/opt/bin/start.sh"]
6672
CMD ["/opt/bin/sabela", "3000", "."]

examples/lean-tactics.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Lean 4: Commands and Tactic Proofs
2+
3+
This notebook demonstrates Lean 4's dual nature as both a programming language and a proof assistant. We define data types and functions (commands), then prove properties about them (tactics).
4+
5+
## Defining a binary tree
6+
7+
An inductive type for binary trees with values at the leaves:
8+
9+
```lean4
10+
inductive Tree (α : Type) where
11+
| leaf : α → Tree α
12+
| node : Tree α → Tree α → Tree α
13+
deriving Repr
14+
```
15+
16+
## Computing with trees
17+
18+
Recursive functions over our tree type — size counts leaves, depth measures the longest path:
19+
20+
```lean4
21+
def Tree.size : Tree α → Nat
22+
| .leaf _ => 1
23+
| .node l r => l.size + r.size
24+
25+
def Tree.depth : Tree α → Nat
26+
| .leaf _ => 0
27+
| .node l r => 1 + max l.depth r.depth
28+
29+
def Tree.map (f : α → β) : Tree α → Tree β
30+
| .leaf x => .leaf (f x)
31+
| .node l r => .node (l.map f) (r.map f)
32+
33+
-- Test with a small tree
34+
def exTree := Tree.node (Tree.node (Tree.leaf 1) (Tree.leaf 2)) (Tree.leaf 3)
35+
36+
#eval exTree.size -- 3
37+
#eval exTree.depth -- 2
38+
```
39+
40+
## Proving size is always positive
41+
42+
Every tree has at least one leaf, so size is always at least 1:
43+
44+
```lean4
45+
theorem Tree.size_pos (t : Tree α) : t.size ≥ 1 := by
46+
induction t with
47+
| leaf _ => simp [Tree.size]
48+
| node l r ihl ihr => simp [Tree.size]; omega
49+
```
50+
51+
## Map preserves structure
52+
53+
Mapping a function over a tree doesn't change its shape:
54+
55+
```lean4
56+
theorem Tree.size_map (f : α → β) (t : Tree α) : (t.map f).size = t.size := by
57+
induction t with
58+
| leaf _ => simp [Tree.map, Tree.size]
59+
| node l r ihl ihr => simp [Tree.map, Tree.size, ihl, ihr]
60+
61+
theorem Tree.depth_map (f : α → β) (t : Tree α) : (t.map f).depth = t.depth := by
62+
induction t with
63+
| leaf _ => simp [Tree.map, Tree.depth]
64+
| node l r ihl ihr => simp [Tree.map, Tree.depth, ihl, ihr]
65+
```
66+
67+
## Depth bounds size
68+
69+
The depth of a tree is always less than its size (since depth counts edges on the longest path, while size counts all leaves):
70+
71+
```lean4
72+
theorem Tree.depth_lt_size (t : Tree α) : t.depth < t.size := by
73+
induction t with
74+
| leaf _ => simp [Tree.size, Tree.depth]
75+
| node l r ihl ihr =>
76+
simp [Tree.size, Tree.depth]
77+
omega
78+
```
79+
80+
## A verified lookup function
81+
82+
Define a mirroring operation and prove it is involutive (applying it twice gives back the original):
83+
84+
```lean4
85+
def Tree.mirror : Tree α → Tree α
86+
| .leaf x => .leaf x
87+
| .node l r => .node r.mirror l.mirror
88+
89+
theorem Tree.mirror_mirror (t : Tree α) : t.mirror.mirror = t := by
90+
induction t with
91+
| leaf _ => simp [Tree.mirror]
92+
| node l r ihl ihr => simp [Tree.mirror, ihl, ihr]
93+
```
94+
95+
## Composing proofs
96+
97+
We can combine earlier results. Mirror preserves size because it just swaps children:
98+
99+
```lean4
100+
theorem Tree.size_mirror (t : Tree α) : t.mirror.size = t.size := by
101+
induction t with
102+
| leaf _ => simp [Tree.mirror, Tree.size]
103+
| node l r ihl ihr =>
104+
simp [Tree.mirror, Tree.size, ihl, ihr]
105+
omega
106+
```

examples/matplotlib-demo.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Python cells can render matplotlib plots inline using base64-encoded PNG.
55
## Setup
66

77
```python
8+
# pip: matplotlib
9+
# pip: numpy
810
import matplotlib
911
matplotlib.use('Agg')
1012
import matplotlib.pyplot as plt

examples/tutorial-lean-integration.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ Lean's real power is theorem proving. A theorem is a type whose inhabitants are
5757
theorem one_plus_one : 1 + 1 = 2 := rfl
5858
5959
-- Proof by simplification
60-
theorem add_zero (n : Nat) : n + 0 = n := by simp
60+
theorem my_add_zero (n : Nat) : n + 0 = n := by simp
6161
6262
-- Proof by induction
63-
theorem add_comm (m n : Nat) : m + n = n + m := by
63+
theorem my_add_comm (m n : Nat) : m + n = n + m := by
6464
induction m with
6565
| zero => simp
66-
| succ m ih => simp [Nat.succ_add, ih]
66+
| succ m ih => omega
6767
```
6868

6969
When a proof succeeds, Sabela shows a green checkmark with "No goals". When it fails, you see the remaining proof state.
@@ -84,12 +84,12 @@ def double (n : Nat) := n * 2
8484
Lean's standard library is available. Imports from any cell are automatically hoisted to the top of the document.
8585

8686
```lean4
87-
import Lean.Data.HashMap
87+
import Std.Data.HashMap
8888
8989
-- This works because the import is hoisted
90-
def myMap := Lean.HashMap.empty.insert "key" 42
90+
def myMap : Std.HashMap String Nat := Std.HashMap.ofList [("key", 42)]
9191
92-
#eval myMap.find? "key"
92+
#eval myMap.get? "key"
9393
```
9494

9595
## Using Mathlib
@@ -138,15 +138,15 @@ putStrLn $ "Fibonacci(15) from Lean: " ++ _bridge_lean_result
138138

139139
## Reactivity
140140

141-
When you edit a Lean cell, only that cell and all cells below it are re-checked (Lean elaborates top-to-bottom). Cells above the edit point are cached by the LSP server, so edits at the bottom of a notebook are fast.
141+
When you edit a Lean cell, that cell and all cells below it are re-executed. Cells are evaluated sequentially from top to bottom, with each cell's environment building on the previous one.
142142

143143
## Cell execution model
144144

145-
Unlike Haskell cells which run individually, all Lean cells are assembled into a single `.lean` file and sent to the Lean language server. This means:
145+
Lean cells are executed one at a time through a JSON REPL, with each cell's definitions chained into the next cell's environment. This means:
146146

147147
- Definitions naturally flow downward
148148
- Errors in one cell may affect cells below it
149-
- The LSP provides incremental checking — only changed regions are re-elaborated
149+
- Each cell gets individual feedback (output or errors)
150150

151151
## Tips
152152

infra/Dockerfile.efs-builder

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
FROM haskell:9.12.2-bookworm
2+
3+
RUN apt-get update && apt-get install -y --no-install-recommends \
4+
python3 \
5+
python3-venv \
6+
curl \
7+
&& rm -rf /var/lib/apt/lists/*
8+
9+
# Copy the REPL source so it's available at runtime
10+
COPY ./repl /opt/repl
11+
12+
# Copy the build script
13+
COPY ./infra/build-efs.sh /opt/build-efs.sh
14+
RUN chmod +x /opt/build-efs.sh
15+
16+
CMD ["/opt/build-efs.sh"]

infra/build-efs.sh

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
EFS="/mnt/efs"
5+
6+
echo "=== EFS Builder: populating $EFS ==="
7+
8+
# ---------- Python venv ----------
9+
echo "[1/4] Creating Python venv..."
10+
python3 -m venv "$EFS/python/venv"
11+
"$EFS/python/venv/bin/pip" install --no-cache-dir --no-compile \
12+
numpy pandas scikit-learn matplotlib
13+
find "$EFS/python/venv" -name '__pycache__' -exec rm -rf {} + 2>/dev/null || true
14+
echo " Python venv ready at $EFS/python/venv"
15+
16+
# ---------- Lean 4 toolchain ----------
17+
echo "[2/4] Installing Lean 4 toolchain..."
18+
export ELAN_HOME="$EFS/lean/.elan"
19+
curl -sSf https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh \
20+
| sh -s -- -y --default-toolchain leanprover/lean4:v4.29.0
21+
export PATH="$ELAN_HOME/bin:$PATH"
22+
echo " Lean installed: $(lean --version)"
23+
24+
# ---------- Lean REPL ----------
25+
echo "[3/4] Building Lean REPL..."
26+
cp -a /opt/repl "$EFS/lean/repl"
27+
cd "$EFS/lean/repl"
28+
lake build
29+
echo " REPL binary at $EFS/lean/repl/.lake/build/bin/repl"
30+
31+
# ---------- Lean + Mathlib ----------
32+
echo "[4/4] Building Lean + Mathlib (this takes a while)..."
33+
mkdir -p "$EFS/lean/lean-base"
34+
cd "$EFS/lean/lean-base"
35+
cat > lakefile.toml <<'TOML'
36+
name = "scratch"
37+
version = "0.1.0"
38+
39+
[[lean_lib]]
40+
name = "Scratch"
41+
42+
[[require]]
43+
name = "mathlib"
44+
scope = "leanprover-community"
45+
TOML
46+
echo 'leanprover/lean4:v4.29.0' > lean-toolchain
47+
lake build
48+
echo " Mathlib built at $EFS/lean/lean-base"
49+
50+
echo ""
51+
echo "=== EFS Builder complete ==="
52+
echo "Contents:"
53+
du -sh "$EFS/python" "$EFS/lean" 2>/dev/null || true

0 commit comments

Comments
 (0)