Project 2: Function Grapher and Analyzer
A graphing calculator that plots functions, shows their behavior (increasing/decreasing, asymptotes, zeros), and allows you to explore how changing parameters affects the shape.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Level 2: Intermediate (The Developer) |
| Main Programming Language | Python |
| Alternative Programming Languages | JavaScript (Canvas), C (with SDL), Rust |
| Coolness Level | Level 3: Genuinely Clever |
| Business Potential | 2. The “Micro-SaaS / Pro Tool” (Solo-Preneur Potential) |
| Knowledge Area | Function Visualization / Numerical Analysis |
| Software or Tool | Graphing Tool |
| Main Book | “Math for Programmers” by Paul Orland |
1. Learning Objectives
By completing this project, you will:
- Translate math definitions into deterministic implementation steps.
- Build validation checks that make correctness observable.
- Diagnose numerical, logical, and data-shape failures early.
- Explain tradeoffs in interviews using evidence from your own build.
2. All Theory Needed (Per-Concept Breakdown)
This project applies the following theory clusters:
- Symbolic-to-numeric translation (expressions, data shapes, invariants)
- Stability constraints (precision, scaling, stopping criteria)
- Optimization or inference logic (depending on project objective)
- Evaluation discipline (error analysis, test coverage, reproducibility)
Concept A: Mathematical Representation Discipline
Fundamentals A math expression is not executable until you define representation, ordering, and domain constraints. The same equation can be represented as a token stream, tree, matrix pipeline, or probability graph. Choosing representation determines what bugs you can catch early.
Deep Dive into the concept Most project failures begin before algorithm selection: they start with ambiguous representation. If your parser cannot distinguish unary minus from subtraction, your calculator fails. If your matrix dimensions are implicit rather than validated, your linear algebra pipeline fails silently. If your probabilistic assumptions (independence, stationarity, or class priors) are not explicit, your inference can look accurate on one split and collapse on another. The core implementation move is to treat representation as a contract. Define each object with shape, domain, and semantic intent. Then enforce invariants at boundaries: input parser, preprocessing, training loop, evaluation stage. This makes debugging local instead of global.
How this fits this project You will encode each operation with explicit contracts and invariant checks.
Definitions & key terms
- Invariant: Property that must hold before and after each operation.
- Shape contract: Expected dimensional structure of vectors/matrices/tensors.
- Domain constraint: Allowed value range (for example log input > 0).
Mental model diagram
User Input -> Representation Layer -> Validated Operation -> Observable Output
(tokens/shapes) (invariants pass) (tests/plots/logs)
How it works
- Parse/ingest data into typed structures.
- Validate shape/domain invariants.
- Execute operation.
- Compare observed output with expected behavior.
- Record failure signature if mismatch appears.
Minimal concrete example
PSEUDOCODE
read expression
tokenize with precedence rules
if token sequence invalid -> return syntax error
evaluate tree
if domain violation -> return bounded diagnostic
print value and confidence check
Common misconceptions
- “If it runs once, representation is correct.” -> false.
- “Type checks are enough without shape checks.” -> false.
Check-your-understanding questions
- Which invariant catches division-by-zero earliest?
- Why does shape validation belong at boundaries rather than only in core logic?
- Predict failure if tokenization ignores unary minus.
Check-your-understanding answers
- Domain check on denominator before operation execution.
- Boundary validation keeps errors local and diagnostic.
- Expressions like
-2^2get misinterpreted and produce wrong precedence behavior.
Real-world applications Feature preprocessing, model-serving input validation, and experiment-tracking schema enforcement.
Where you’ll apply it This project and every downstream project in the sprint.
References
- CSAPP (Bryant & O’Hallaron), floating-point chapter
- Math for Programmers (Paul Orland), representation-oriented chapters
Key insight Correct representation reduces the complexity of every later decision.
Summary Stable ML math implementations start with explicit contracts, not implicit assumptions.
Homework/Exercises
- Write five invariants for your project.
- Build a failing test input for each invariant.
Solutions
- Include at least one shape, one domain, one convergence, one reproducibility, and one output-range invariant.
- Each failing input should trigger exactly one diagnostic to keep root-cause analysis clean.
3. Build Blueprint
- Scope the smallest end-to-end slice that produces visible output.
- Add deterministic tests and edge-case probes.
- Layer complexity only after baseline behavior is stable.
- Add metrics logging before optimization.
- Run failure drills: perturb inputs, scale values, and check stability.
4. Real-World Outcome (Target)
$ python grapher.py "sin(x) * exp(-x/10)" -10 10
[Opens window showing damped sine wave]
[Markers at zeros: x ≈ 0, 3.14, 6.28, ...]
[Shaded regions: green where increasing, red where decreasing]
$ python grapher.py "1/x" -5 5
[Shows hyperbola with vertical asymptote at x=0 marked]
Implementation Hints:
Map mathematical coordinates to screen pixels: screen_x = (math_x - x_min) / (x_max - x_min) * width. Sample the function at each pixel column. For zeros, use bisection: if f(a) and f(b) have opposite signs, there’s a zero between them.
To detect increasing/decreasing without calculus: compare f(x+ε) with f(x). This is actually computing the derivative numerically! You’re building intuition for calculus without calling it that.
Learning milestones:
- Linear and quadratic functions plot correctly → You understand basic function shapes
- Exponential/logarithmic functions show growth/decay → You understand these crucial ML functions
- Interactive parameter changes show function families → You understand parameterized models (core ML concept!)
5. Core Design Notes from Main Guide
Core Question
“Why do we need to SEE mathematics, not just compute it?”
A function like f(x) = x^2 - 4 can be understood algebraically (it equals zero when x = 2 or x = -2), but when you see the parabola crossing the x-axis at those points, something deeper happens in your brain. You understand that the function has a minimum at x = 0, that it’s symmetric, that it grows faster and faster as you move away from the center. This visual intuition is exactly what you need for machine learning, where loss functions form “landscapes” that gradient descent must navigate. By building a grapher, you develop the visual intuition that separates those who merely use ML from those who truly understand it.
Concepts You Must Understand First
Stop and research these before coding:
- The Cartesian Coordinate System
- How do (x, y) pairs map to points on a plane?
- What does it mean for a function to be “continuous”?
- How do you handle different scales (zooming in/out)?
- Book Reference: “Math for Programmers” Chapter 3 - Paul Orland
- Function Behavior and Shape
- What makes a parabola different from a line or a cubic?
- What are asymptotes and why do they matter?
- How can you tell where a function is increasing or decreasing?
- Book Reference: “Calculus: Early Transcendentals” Chapter 1 - James Stewart
- Root Finding Algorithms (Bisection and Newton-Raphson)
- What does it mean for a function to have a “root” or “zero”?
- How does the bisection method work geometrically?
- Why is Newton-Raphson faster but less reliable?
- Book Reference: “Algorithms” Section 4.2 - Sedgewick & Wayne
- Numerical Derivatives
- How can you approximate the slope of a function at a point?
- What is the finite difference formula: (f(x+h) - f(x-h)) / 2h?
- Why does choosing h matter so much?
- Book Reference: “Numerical Recipes” Chapter 5 - Press et al.
- Parameterized Function Families
- What does it mean when y = ax^2 + bx + c has “parameters” a, b, c?
- How does changing a parameter affect the function’s shape?
- Why is this concept central to machine learning?
- Book Reference: “Hands-On Machine Learning” Chapter 4 - Aurelien Geron
Questions to Guide Your Design
Before implementing, think through these:
- How will you map mathematical coordinates to pixel coordinates on the screen?
- What happens when the function is undefined (like 1/x at x=0)?
- How will you detect and mark zeros of the function?
- How will you handle very large or very small function values?
- Should you draw lines between sample points, or just points? What’s the trade-off?
- How will you implement smooth zooming and panning?
Thinking Exercise
Before coding, work through this on paper:
Consider the function f(x) = x^3 - 3x + 1 on the interval [-3, 3].
-
Find the zeros manually: Where does f(x) = 0? Try x = -2: f(-2) = -8 + 6 + 1 = -1. Try x = -1.5: f(-1.5) = -3.375 + 4.5 + 1 = 2.125. Since f(-2) < 0 and f(-1.5) > 0, there’s a zero between -2 and -1.5. Use bisection to narrow it down.
-
Find where it’s increasing/decreasing: The derivative f’(x) = 3x^2 - 3 = 3(x^2 - 1) = 3(x-1)(x+1). So f’(x) = 0 at x = -1 and x = 1. Check: f’(0) = -3 < 0, so decreasing between -1 and 1. f’(2) = 9 > 0, so increasing outside that interval.
-
Sketch the curve: Mark the local maximum at x = -1 where f(-1) = 3, and local minimum at x = 1 where f(1) = -1. Connect the dots respecting the increasing/decreasing regions.
Now implement code that discovers all of this automatically!
Interview Questions
- “How would you plot a function that has a vertical asymptote, like f(x) = 1/x?”
- Expected: Detect when function values exceed a threshold; don’t connect points across the asymptote; optionally draw a dashed vertical line.
- “Explain how you would find the zeros of a function numerically.”
- Expected: Describe bisection (guaranteed to converge if you bracket a sign change) or Newton-Raphson (faster but may diverge).
- “How would you determine where a function is increasing or decreasing without computing the derivative symbolically?”
- Expected: Use numerical derivatives: compare f(x+h) with f(x-h) for small h. If the difference is positive, function is increasing.
- “What’s the time complexity of plotting a function over an interval with N sample points?”
- Expected: O(N) function evaluations, O(N) for drawing. Root finding adds O(log(1/epsilon)) per root for bisection.
- “How would you implement smooth zooming that feels natural to the user?”
- Expected: Zoom toward mouse position, not screen center. Use exponential scaling. Recalculate sample points dynamically.
- “What happens if the user enters a function that takes a long time to compute?”
- Expected: Use progressive rendering, timeouts, or background threads. Show partial results while computing.
Hints in Layers (Treat as pseudocode guidance)
Hint 1: Start with the coordinate transformation
The key formula is: screen_x = (math_x - x_min) / (x_max - x_min) * width. Master this transformation first with a simple line y = x before trying complex functions.
Hint 2: Sample the function at each pixel column For a window of width W pixels, you need W sample points. At each pixel column i, compute math_x = x_min + i * (x_max - x_min) / W, then evaluate f(math_x).
Hint 3: Handle undefined values gracefully When evaluating f(x), wrap it in a try/except. If you get an error (like division by zero), mark that point as undefined and don’t draw a line to/from it.
Hint 4: For root finding, use the Intermediate Value Theorem If f(a) and f(b) have opposite signs, there’s at least one root between a and b. Iterate: compute midpoint m, check sign of f(m), narrow the interval.
Hint 5: For increasing/decreasing regions, compare consecutive points If f(x_{i+1}) > f(x_i), the function is increasing in that region. You can color-code segments based on this.
Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Functions and their graphs | “Math for Programmers” | Chapter 3 - Paul Orland |
| Coordinate systems and transformations | “Computer Graphics from Scratch” | Chapter 1 - Gabriel Gambetta |
| Numerical root finding | “Algorithms” | Section 4.2 - Sedgewick & Wayne |
| Continuity and limits | “Calculus: Early Transcendentals” | Chapter 2 - James Stewart |
| Numerical differentiation | “Numerical Recipes” | Chapter 5 - Press et al. |
| Interactive visualization | “The Nature of Code” | Chapter 1 - Daniel Shiffman |
| Parameterized models in ML | “Hands-On Machine Learning” | Chapter 4 - Aurelien Geron |
6. Validation, Pitfalls, and Completion
Common Pitfalls and Debugging
Problem 1: “Outputs drift after a few iterations”
- Why: Hidden numerical instability (unscaled features, aggressive step size, or repeated subtraction of nearly equal values).
- Fix: Normalize inputs, reduce step size, and track relative error rather than only absolute error.
- Quick test: Run the same task with two scales of input (for example x and 10x) and compare normalized error curves.
Problem 2: “Results are inconsistent across runs”
- Why: Random seeds, data split randomness, or non-deterministic ordering are uncontrolled.
- Fix: Set seeds, log configuration, and store split indices and hyperparameters with each run.
- Quick test: Re-run three times with the same seed and confirm metrics remain inside a tight tolerance band.
Problem 3: “The project works on the demo case but fails on edge cases”
- Why: Tests only cover happy-path inputs.
- Fix: Add adversarial inputs (empty values, extreme ranges, near-singular matrices, rare classes).
- Quick test: Build an edge-case test matrix and ensure every scenario reports expected behavior.
Definition of Done
- Core functionality works on reference inputs
- Edge cases are tested and documented
- Results are reproducible (seeded and versioned configuration)
- Performance or convergence behavior is measured and explained
- A short retrospective explains what failed first and how you fixed it
7. Extension Ideas
- Add a stress-test mode with adversarial inputs.
- Add a short benchmark report (runtime + memory + error trend).
- Add a reproducibility bundle (seed, config, and fixed test corpus).
8. Why This Project Matters
Seeing functions visually builds intuition that equations alone cannot provide. When you implement zooming/panning, you confront concepts like limits and continuity. Finding zeros and extrema prepares you for optimization.
This project is valuable because it creates observable evidence of mathematical reasoning under real implementation constraints.