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
49 changes: 45 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,15 +372,54 @@ Each demo module must:

## PR and Change Guidelines

### Before Submitting Changes
### Before Submitting Changes - Required CI Checks

**CRITICAL: Always run these checks before committing to ensure CI passes:**

```bash
# 1. Install dependencies
python -m pip install -e ".[dev]"

# 2. Run all pre-commit hooks (includes format, lint, and other checks)
pre-commit run --all-files --show-diff-on-failure --color=always

# 3. Run full test suite
pytest -q

# 4. Verify no linting errors remain
ruff check .
```

**Pre-commit Hook Checks (must all pass):**
- ✅ `ruff` - Linting (E, F, I, B rules)
- ✅ `ruff-format` - Code formatting
- ✅ `trailing-whitespace` - Remove trailing whitespace
- ✅ `end-of-file-fixer` - Ensure files end with newline
- ✅ `check-merge-conflicts` - Check for merge conflict markers

**If pre-commit hooks fail:**
1. Review the error output carefully
2. Fix issues manually or use `ruff check --fix` for auto-fixes
3. Re-run `pre-commit run --all-files` until all checks pass
4. Commit the fixes

**Common Issues:**
- Trailing whitespace: Run `pre-commit run trailing-whitespace --all-files`
- Import ordering: Run `ruff check --fix` to auto-sort imports
- Formatting: Run `ruff format .` to format all files

### Standard Pre-Commit Workflow

1. **Run tests:** `pytest -q` must pass
2. **Format code:** `ruff format .`
3. **Fix linting:** `ruff check --fix`
4. **Check coverage:** Maintain or improve test coverage
5. **Update docs:** If adding new features, update relevant documentation
4. **Run pre-commit:** `pre-commit run --all-files`
5. **Check coverage:** Maintain or improve test coverage
6. **Update docs:** If adding new features, update relevant documentation

### PR Acceptance Criteria
- All tests pass
- All tests pass (208/208 currently)
- All pre-commit hooks pass
- Code is properly formatted (Ruff)
- No new linting errors
- Test coverage maintained or improved
Expand All @@ -395,6 +434,8 @@ Each demo module must:
- [ ] Code follows existing style conventions
- [ ] Demo function exists and works
- [ ] No breaking changes to existing APIs
- [ ] All pre-commit hooks pass
- [ ] CI checks will pass (verified locally)

---

Expand Down
124 changes: 108 additions & 16 deletions flask_app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,53 @@
"two_pointers": """Summary: Pair pointers moving toward/along an array.
Typical Time: O(n), Space: O(1)
Use: Two-sum in sorted array, dedup, merging, partitioning.
""",
# Robotics Navigation
"wall_following": """Summary: Reactive navigation using left/right-hand rule along walls.
Time: O(P) where P is perimeter of obstacles
Space: O(1) for basic version
Use: Maze navigation with unknown environment, bump sensors only.
Pitfall: May not find goal if obstacles disconnected; doesn't guarantee shortest path.
""",
"bug_algorithms": """Summary: Bug1/Bug2 goal-seeking with obstacle circumnavigation.
Bug1: Traverse full perimeter, leave at closest point. Time: O(n*P)
Bug2: Follow m-line, leave when closer than hit point. Often more efficient.
Both: Provably complete, reactive (local sensing), not optimal.
Use: Goal-seeking in unknown environments with guaranteed completeness.
""",
"pledge_algorithm": """Summary: Wall-following with cumulative rotation tracking.
Time: O(P) where P is obstacle perimeter
Space: O(1) rotation counter
Use: Escape closed loops; resume straight motion when rotation sum = 0.
Advantage: More sophisticated than basic wall-following.
""",
"potential_fields": """Summary: Artificial potential fields for reactive navigation.
Time: O(steps * obstacles) per iteration
Space: O(1) for force calculations
Use: Real-time reactive planning; attractive force to goal, repulsive from obstacles.
Pitfall: Can get stuck in local minima (e.g., U-shaped obstacles).
Parameters: attractive_gain, repulsive_gain, influence_distance need tuning.
""",
"rrt": """Summary: Rapidly-exploring Random Tree sampling-based planner.
Time: O(iterations * tree_size) for nearest neighbor search
Space: O(tree_size) for storing nodes
Use: High-dimensional configuration spaces, complex obstacles.
Probabilistically complete; not optimal (variants: RRT*, RRT-Connect).
Parameters: step_size, goal_sample_rate, max_iterations affect performance.
""",
"particle_filter": """Summary: Monte Carlo localization using particle set representation.
Time: O(N * M) where N=particles, M=landmarks
Space: O(N) for particle set
Steps: Predict (motion model), Update (measurement likelihood), Resample.
Use: Non-parametric belief, multimodal distributions, global localization.
Pitfall: Particle depletion; add noise during resampling.
""",
"occupancy_grid": """Summary: Probabilistic environment mapping with log-odds.
Time: O(rays * cells_per_ray) per update
Space: O(width * height) for grid
Use: SLAM, obstacle detection, autonomous navigation.
Log-odds prevents numerical issues; ray-based sensor model.
Cells along ray marked free, endpoint marked occupied.
""",
}

Expand Down Expand Up @@ -344,7 +391,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 +523,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 +620,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 @@ -622,16 +667,67 @@ def api_viz_path():
return jsonify({"error": error}), 500


@app.get("/viz/robotics")
def viz_robotics():
# Render robotics navigation visualization page
algo = request.args.get("algo", "wall_left")
try:
from flask_app.visualizations import robotics_viz as r_viz # type: ignore

algorithms = [{"key": k, "name": v["name"]} for k, v in r_viz.ALGORITHMS.items()]
except Exception:
algorithms = [
{"key": "wall_left", "name": "Wall Following (Left-Hand)"},
{"key": "wall_right", "name": "Wall Following (Right-Hand)"},
{"key": "bug1", "name": "Bug1 Algorithm"},
{"key": "pledge", "name": "Pledge Algorithm"},
{"key": "potential", "name": "Potential Fields"},
{"key": "rrt", "name": "RRT"},
]
return render_template(
"viz_robotics.html",
algo=algo,
algorithms=algorithms,
notes=get_notes(
{
"wall_left": "wall_following",
"wall_right": "wall_following",
"bug1": "bug_algorithms",
"pledge": "pledge_algorithm",
"potential": "potential_fields",
"rrt": "rrt",
}.get(algo, algo)
),
)


@app.get("/viz/robotics/data")
def api_viz_robotics():
algo = request.args.get("algo", "wall_left")
rows = int(request.args.get("rows", 15))
cols = int(request.args.get("cols", 20))
density = float(request.args.get("density", 0.2))
seed = request.args.get("seed", None)
seed = int(seed) if seed not in (None, "", "null") else None

try:
from flask_app.visualizations import robotics_viz as r_viz # type: ignore

result = r_viz.visualize(algo, rows=rows, cols=cols, density=density, seed=seed)
return jsonify(result)
except Exception as e:
error = "".join(traceback.format_exception(type(e), e, e.__traceback__))
return jsonify({"error": error}), 500


@app.get("/viz/arrays")
def viz_arrays():
# Render array techniques visualization page (Binary Search / Two-Pointers / Sliding Window)
algo = request.args.get("algo", "binary_search")
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 +786,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 +832,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)
Loading
Loading