Skip to content
Closed
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
447 changes: 424 additions & 23 deletions AGENTS.md

Large diffs are not rendered by default.

24 changes: 8 additions & 16 deletions flask_app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,9 @@ def run_demo(module_name: str) -> str:
try:
mod = importlib.import_module(module_name)
except ModuleNotFoundError as e:
raise ModuleNotFoundError(f"Could not import module {module_name!r}. Ensure it is a valid demo id.") from e
raise ModuleNotFoundError(
f"Could not import module {module_name!r}. Ensure it is a valid demo id."
) from e

demo_fn = getattr(mod, "demo", None)
if not callable(demo_fn):
Expand Down Expand Up @@ -474,9 +476,7 @@ def viz_sorting():
try:
from flask_app.visualizations import sorting_viz as s_viz # type: ignore

algorithms = [
{"key": k, "name": v["name"]} for k, v in s_viz.ALGORITHMS.items()
]
algorithms = [{"key": k, "name": v["name"]} for k, v in s_viz.ALGORITHMS.items()]
except Exception:
algorithms = [
{"key": "quick", "name": "Quick Sort"},
Expand Down Expand Up @@ -573,9 +573,7 @@ def viz_path():
try:
from flask_app.visualizations import path_viz as p_viz # type: ignore

algorithms = [
{"key": k, "name": v["name"]} for k, v in p_viz.ALGORITHMS.items()
]
algorithms = [{"key": k, "name": v["name"]} for k, v in p_viz.ALGORITHMS.items()]
except Exception:
algorithms = [
{"key": "astar", "name": "A* (Manhattan)"},
Expand Down Expand Up @@ -629,9 +627,7 @@ def viz_arrays():
try:
from flask_app.visualizations import array_viz as a_viz # type: ignore

algorithms = [
{"key": k, "name": v["name"]} for k, v in a_viz.ALGORITHMS.items()
]
algorithms = [{"key": k, "name": v["name"]} for k, v in a_viz.ALGORITHMS.items()]
except Exception:
algorithms = [
{"key": "binary_search", "name": "Binary Search"},
Expand Down Expand Up @@ -690,9 +686,7 @@ def viz_mst():
try:
from flask_app.visualizations import mst_viz as m_viz # type: ignore

algorithms = [
{"key": k, "name": v["name"]} for k, v in m_viz.ALGORITHMS.items()
]
algorithms = [{"key": k, "name": v["name"]} for k, v in m_viz.ALGORITHMS.items()]
except Exception:
algorithms = [
{"key": "kruskal", "name": "Minimum Spanning Tree (Kruskal)"},
Expand Down Expand Up @@ -738,9 +732,7 @@ def viz_topo():
try:
from flask_app.visualizations import topo_viz as t_viz # type: ignore

algorithms = [
{"key": k, "name": v["name"]} for k, v in t_viz.ALGORITHMS.items()
]
algorithms = [{"key": k, "name": v["name"]} for k, v in t_viz.ALGORITHMS.items()]
except Exception:
algorithms = [
{"key": "kahn", "name": "Topological Sort (Kahn's Algorithm)"},
Expand Down
3 changes: 3 additions & 0 deletions flask_app/docs_server.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import os

from flask import Flask, send_from_directory

app = Flask(__name__)

# Path to built MkDocs site
DOCS_BUILD_DIR = os.path.join(os.path.dirname(__file__), "..", "site")


@app.route("/docs/")
@app.route("/docs/<path:filename>")
def serve_docs(filename="index.html"):
Expand All @@ -14,5 +16,6 @@ def serve_docs(filename="index.html"):
"""
return send_from_directory(DOCS_BUILD_DIR, filename)


if __name__ == "__main__":
app.run(host="127.0.0.1", port=5003, debug=True)
16 changes: 4 additions & 12 deletions flask_app/visualizations/array_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ def binary_search_frames(
arr: list[int], target: int, max_steps: int = 20000
) -> list[dict[str, Any]]:
a = arr[:]
frames: list[dict[str, Any]] = [
_snap(a, "init", lo=0, hi=len(a) - 1, mid=None, found=False)
]
frames: list[dict[str, Any]] = [_snap(a, "init", lo=0, hi=len(a) - 1, mid=None, found=False)]
lo, hi = 0, len(a) - 1
steps = 0
while lo <= hi and steps < max_steps:
Expand Down Expand Up @@ -71,14 +69,10 @@ def two_pointers_sum_frames(
return frames
if s < target:
left += 1
frames.append(
_snap(a, "move-left", l=left, r=right, sum=None, target=target)
)
frames.append(_snap(a, "move-left", l=left, r=right, sum=None, target=target))
else:
right -= 1
frames.append(
_snap(a, "move-right", l=left, r=right, sum=None, target=target)
)
frames.append(_snap(a, "move-right", l=left, r=right, sum=None, target=target))
steps += 1
frames.append(_snap(a, "not-found", l=left, r=right, sum=None, target=target))
return frames
Expand All @@ -93,9 +87,7 @@ def sliding_window_min_len_geq_frames(
"""
a = arr[:]
frames: list[dict[str, Any]] = [
_snap(
a, "init", win_l=0, win_r=-1, best_l=None, best_r=None, s=0, target=target
)
_snap(a, "init", win_l=0, win_r=-1, best_l=None, best_r=None, s=0, target=target)
]
n = len(a)
s = 0
Expand Down
12 changes: 3 additions & 9 deletions flask_app/visualizations/graph_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ def union(a: int, b: int) -> None:
edges.add(e)


def generate_graph(
n: int = 12, p: float = 0.25, seed: int | None = None
) -> dict[str, Any]:
def generate_graph(n: int = 12, p: float = 0.25, seed: int | None = None) -> dict[str, Any]:
"""
Generate an undirected simple graph with n nodes.
- Start with no edges, add each possible edge with probability p
Expand Down Expand Up @@ -85,9 +83,7 @@ def _frame(state: dict[str, Any]) -> dict[str, Any]:
}


def bfs_frames(
g: dict[str, Any], start: int = 0, max_steps: int = 20000
) -> list[dict[str, Any]]:
def bfs_frames(g: dict[str, Any], start: int = 0, max_steps: int = 20000) -> list[dict[str, Any]]:
n = g["n"]
adj: list[list[int]] = [[] for _ in range(n)]
for u, v in g["edges"]:
Expand Down Expand Up @@ -152,9 +148,7 @@ def bfs_frames(
return frames


def dfs_frames(
g: dict[str, Any], start: int = 0, max_steps: int = 20000
) -> list[dict[str, Any]]:
def dfs_frames(g: dict[str, Any], start: int = 0, max_steps: int = 20000) -> list[dict[str, Any]]:
n = g["n"]
adj: list[list[int]] = [[] for _ in range(n)]
for u, v in g["edges"]:
Expand Down
8 changes: 2 additions & 6 deletions flask_app/visualizations/mst_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
Edge = tuple[int, int, float]


def _circle_layout(
n: int, jitter: float = 0.0, rng: random.Random | None = None
) -> list[Coord]:
def _circle_layout(n: int, jitter: float = 0.0, rng: random.Random | None = None) -> list[Coord]:
pts: list[Coord] = []
rng = rng or random.Random()
for i in range(n):
Expand Down Expand Up @@ -131,9 +129,7 @@ def union(a: int, b: int) -> bool:
return frames


def prim_frames(
g: dict[str, Any], start: int = 0, max_steps: int = 50000
) -> list[dict[str, Any]]:
def prim_frames(g: dict[str, Any], start: int = 0, max_steps: int = 50000) -> list[dict[str, Any]]:
n: int = g["n"]
edges: list[Edge] = g["edges"]
# Build adjacency
Expand Down
10 changes: 2 additions & 8 deletions flask_app/visualizations/nn_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,7 @@ def __init__(self, hidden: int, lr: float, seed: int | None = None) -> None:
def forward(self, x: Point) -> tuple[list[float], float]:
# x: (2,)
# z1 = W1 x + b1
z1 = [
self.W1[i][0] * x[0] + self.W1[i][1] * x[1] + self.b1[i]
for i in range(self.h)
]
z1 = [self.W1[i][0] * x[0] + self.W1[i][1] * x[1] + self.b1[i] for i in range(self.h)]
a1 = [_tanh(z) for z in z1]
# z2 = W2 a1 + b2
z2 = sum(self.W2[i] * a1[i] for i in range(self.h)) + self.b2
Expand Down Expand Up @@ -137,10 +134,7 @@ def backward_update(self, x: Point, y: int) -> float:

# loss for monitoring (BCE)
eps = 1e-9
loss = -(
float(y) * math.log(yhat + eps)
+ (1.0 - float(y)) * math.log(1.0 - yhat + eps)
)
loss = -(float(y) * math.log(yhat + eps) + (1.0 - float(y)) * math.log(1.0 - yhat + eps))
return loss

def predict_proba(self, x: Point) -> float:
Expand Down
36 changes: 9 additions & 27 deletions flask_app/visualizations/path_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,7 @@ def a_star_frames(grid: dict[str, Any], max_steps: int = 50000) -> list[dict[str
came_from: dict[Coord, Coord] = {}
g_score: dict[Coord, int] = {start: 0}

frames: list[dict[str, Any]] = [
_frame(None, list(open_set), list(closed_set), [], "init")
]
frames: list[dict[str, Any]] = [_frame(None, list(open_set), list(closed_set), [], "init")]

while open_heap and len(frames) < max_steps:
_, _, current = heapq.heappop(open_heap)
Expand All @@ -105,9 +103,7 @@ def a_star_frames(grid: dict[str, Any], max_steps: int = 50000) -> list[dict[str

if current == goal:
path = _reconstruct_path(came_from, current)
frames.append(
_frame(current, list(open_set), list(closed_set), path, "done")
)
frames.append(_frame(current, list(open_set), list(closed_set), path, "done"))
return frames

closed_set.add(current)
Expand All @@ -125,18 +121,14 @@ def a_star_frames(grid: dict[str, Any], max_steps: int = 50000) -> list[dict[str
heapq.heappush(open_heap, (f, tie, nbr))
open_set.add(nbr)
p = _reconstruct_path(came_from, current)
frames.append(
_frame(nbr, list(open_set), list(closed_set), p, "push/update")
)
frames.append(_frame(nbr, list(open_set), list(closed_set), p, "push/update"))

# No path found
frames.append(_frame(None, list(open_set), list(closed_set), [], "no-path"))
return frames


def dijkstra_frames(
grid: dict[str, Any], max_steps: int = 50000
) -> list[dict[str, Any]]:
def dijkstra_frames(grid: dict[str, Any], max_steps: int = 50000) -> list[dict[str, Any]]:
rows, cols = grid["rows"], grid["cols"]
walls = set(map(tuple, grid["walls"]))
start: Coord = tuple(grid["start"]) # type: ignore
Expand All @@ -151,9 +143,7 @@ def dijkstra_frames(
came_from: dict[Coord, Coord] = {}
dist: dict[Coord, int] = {start: 0}

frames: list[dict[str, Any]] = [
_frame(None, list(open_set), list(closed_set), [], "init")
]
frames: list[dict[str, Any]] = [_frame(None, list(open_set), list(closed_set), [], "init")]

while open_heap and len(frames) < max_steps:
_, _, current = heapq.heappop(open_heap)
Expand All @@ -164,9 +154,7 @@ def dijkstra_frames(

if current == goal:
path = _reconstruct_path(came_from, current)
frames.append(
_frame(current, list(open_set), list(closed_set), path, "done")
)
frames.append(_frame(current, list(open_set), list(closed_set), path, "done"))
return frames

closed_set.add(current)
Expand All @@ -183,9 +171,7 @@ def dijkstra_frames(
heapq.heappush(open_heap, (nd, tie, nbr))
open_set.add(nbr)
p = _reconstruct_path(came_from, current)
frames.append(
_frame(nbr, list(open_set), list(closed_set), p, "push/update")
)
frames.append(_frame(nbr, list(open_set), list(closed_set), p, "push/update"))

frames.append(_frame(None, list(open_set), list(closed_set), [], "no-path"))
return frames
Expand Down Expand Up @@ -246,9 +232,7 @@ def gbfs_frames(grid: dict[str, Any], max_steps: int = 50000) -> list[dict[str,
closed_set: set[Coord] = set()
came_from: dict[Coord, Coord] = {}

frames: list[dict[str, Any]] = [
_frame(None, list(open_set), list(closed_set), [], "init")
]
frames: list[dict[str, Any]] = [_frame(None, list(open_set), list(closed_set), [], "init")]

while open_heap and len(frames) < max_steps:
_, _, current = heapq.heappop(open_heap)
Expand All @@ -259,9 +243,7 @@ def gbfs_frames(grid: dict[str, Any], max_steps: int = 50000) -> list[dict[str,

if current == goal:
path = _reconstruct_path(came_from, current)
frames.append(
_frame(current, list(open_set), list(closed_set), path, "done")
)
frames.append(_frame(current, list(open_set), list(closed_set), path, "done"))
return frames

closed_set.add(current)
Expand Down
8 changes: 2 additions & 6 deletions flask_app/visualizations/sorting_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ def _snap(
return {"arr": arr[:], "a": a, "b": b, "op": op}


def generate_array(
n: int = 30, seed: int | None = None, unique: bool = True
) -> list[int]:
def generate_array(n: int = 30, seed: int | None = None, unique: bool = True) -> list[int]:
"""
Generate a random array for visualization.
- If unique: values are 1..n shuffled
Expand Down Expand Up @@ -47,9 +45,7 @@ def bubble_sort_frames(arr: list[int], max_steps: int = 20000) -> list[dict[str,
return frames


def insertion_sort_frames(
arr: list[int], max_steps: int = 20000
) -> list[dict[str, Any]]:
def insertion_sort_frames(arr: list[int], max_steps: int = 20000) -> list[dict[str, Any]]:
a = arr[:]
frames: list[dict[str, Any]] = [_snap(a)]
for i in range(1, len(a)):
Expand Down
3 changes: 1 addition & 2 deletions flask_app/visualizations/topo_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ def generate_dag(
rng = random.Random(seed)
coords = _layer_layout(n, layers)
nodes = [
{"id": i, "x": coords[i][0], "y": coords[i][1], "layer": coords[i][2]}
for i in range(n)
{"id": i, "x": coords[i][0], "y": coords[i][1], "layer": coords[i][2]} for i in range(n)
]

# group node ids by layer
Expand Down
Loading