diff --git a/content/posts/euler-identity.mdx b/content/posts/euler-identity.mdx deleted file mode 100644 index 9a01f72..0000000 --- a/content/posts/euler-identity.mdx +++ /dev/null @@ -1,185 +0,0 @@ ---- -title: "Euler's Identity: The Most Beautiful Equation" -date: "2025-02-20" -excerpt: "Exploring the deepest connection between five fundamental constants in mathematics and why Euler's identity is considered the most beautiful equation." -author: Maria Gonzalez -tags: - - mathematics - - euler - - complex-numbers -coverImage: /images/euler-cover.jpg ---- - -Euler's identity is widely regarded as the most beautiful equation in all of mathematics: - -$$e^{i\pi} + 1 = 0$$ - -This single line unites five fundamental mathematical constants into one deceptively simple relationship: - -- **$e$** — Euler's number, the base of the natural logarithm ($e \approx 2.71828\ldots$) -- **$i$** — The imaginary unit, defined by $i^2 = -1$ -- **$\pi$** — Archimedes' constant, the ratio of a circle's circumference to its diameter ($\pi \approx 3.14159\ldots$) -- **$1$** — The multiplicative identity -- **$0$** — The additive identity - -Beyond the constants themselves, the equation weaves together three fundamental operations — addition, multiplication, and exponentiation — and bridges two branches of mathematics that historically seemed unrelated: algebra and geometry. In this post, we will explore why this identity is true, tracing the path from power series to geometric intuition. - -## Historical Context: Euler and the Birth of Complex Exponentials - -Leonhard Euler (1707–1783) was one of the most prolific mathematicians in history. Born in Basel, Switzerland, he published over 800 papers and fundamentally shaped analysis, number theory, graph theory, and mathematical physics. Despite losing most of his vision in later life — he went completely blind in 1771 — his mental calculation abilities remained extraordinary. - -The identity that now bears his name emerged gradually through the work of several mathematicians. John Wallis first explored complex exponents in his 1685 work *De Algebra*, but it was Euler who, in his 1748 treatise *Introductio in Analysin Infinitorum*, established the general formula: - -$$e^{ix} = \cos x + i\sin x$$ - -Euler presented this without proof, assuming his audience would accept it as a natural extension of the real exponential function. It was not until later, with the rigorous development of analysis by Cauchy and Weierstrass in the 19th century, that the identity received a solid foundation. Today, we recognize Euler's formula as one of the most important results in all of mathematics. - -## The Power Series Foundation - -To understand *why* Euler's formula holds, we must begin with the power series (Taylor series) definitions of the key functions involved. The exponential function, sine, and cosine all admit infinite series representations that converge for every complex number $z$. - -The exponential function is defined by: - -$$e^z = \sum_{n=0}^{\infty} \frac{z^n}{n!} = 1 + z + \frac{z^2}{2!} + \frac{z^3}{3!} + \frac{z^4}{4!} + \frac{z^5}{5!} + \cdots$$ - -The trigonometric functions are defined as: - -$$\cos z = \sum_{n=0}^{\infty} \frac{(-1)^n z^{2n}}{(2n)!} = 1 - \frac{z^2}{2!} + \frac{z^4}{4!} - \frac{z^6}{6!} + \cdots$$ - -$$\sin z = \sum_{n=0}^{\infty} \frac{(-1)^n z^{2n+1}}{(2n+1)!} = z - \frac{z^3}{3!} + \frac{z^5}{5!} - \frac{z^7}{7!} + \cdots$$ - -These series all have an infinite radius of convergence, meaning they define entire functions on the complex plane. Now, let us substitute $z = ix$ (where $x$ is real and $i^2 = -1$) into the exponential series: - -$$e^{ix} = 1 + (ix) + \frac{(ix)^2}{2!} + \frac{(ix)^3}{3!} + \frac{(ix)^4}{4!} + \frac{(ix)^5}{5!} + \frac{(ix)^6}{6!} + \frac{(ix)^7}{7!} + \frac{(ix)^8}{8!} + \cdots$$ - -Using the powers of $i$: $i^0 = 1$, $i^1 = i$, $i^2 = -1$, $i^3 = -i$, $i^4 = 1$, $i^5 = i$, $i^6 = -1$, $i^7 = -i$, $i^8 = 1$, and so on, we see the terms cycle through $1, i, -1, -i$: - -$$e^{ix} = 1 + ix - \frac{x^2}{2!} - i\frac{x^3}{3!} + \frac{x^4}{4!} + i\frac{x^5}{5!} - \frac{x^6}{6!} - i\frac{x^7}{7!} + \frac{x^8}{8!} + \cdots$$ - -Now, group the real terms and the imaginary terms: - -$$e^{ix} = \left(1 - \frac{x^2}{2!} + \frac{x^4}{4!} - \frac{x^6}{6!} + \frac{x^8}{8!} - \cdots\right) + i\left(x - \frac{x^3}{3!} + \frac{x^5}{5!} - \frac{x^7}{7!} + \cdots\right)$$ - -The real part is exactly the Taylor series for $\cos x$, and the imaginary part is exactly the Taylor series for $\sin x$. Therefore: - -$$e^{ix} = \cos x + i\sin x$$ - -This elegant derivation shows that Euler's formula is not a mysterious accident — it is the natural consequence of how power series behave when extended to the complex domain. - -## A Geometric Interpretation: The Complex Plane - -While the power series derivation is rigorous and convincing, there is a deeper geometric reason why Euler's formula is true. To see this, we must visualize complex numbers not as algebraic abstractions, but as points (or vectors) in the plane. - -Every complex number $z = a + bi$ can be represented as a point in the complex plane with real coordinate $a$ and imaginary coordinate $b$. We can also describe it using polar coordinates $(r, \theta)$, where $r = |z| = \sqrt{a^2 + b^2}$ is the modulus (distance from the origin) and $\theta$ is the argument (angle from the positive real axis). In this notation: - -$$z = r(\cos\theta + i\sin\theta)$$ - -Now consider the function $f(x) = e^{ix}$ for real $x$. From the polar perspective, the modulus of $e^{ix}$ is: - -$$|e^{ix}| = \sqrt{\cos^2 x + \sin^2 x} = 1$$ - -Thus, $e^{ix}$ always lies on the unit circle in the complex plane. Its argument is exactly $x$ radians, so we can interpret $e^{ix}$ as the point on the unit circle reached by rotating counterclockwise from $(1, 0)$ by $x$ radians. - -This gives us a powerful geometric intuition: **the exponential with an imaginary exponent performs rotation**. The quantity $e^{ix}$ rotates the point $1$ (on the real axis) by an angle $x$ counterclockwise around the unit circle. When $x = \pi$, we have rotated exactly halfway around the circle, landing at $-1$: - -$$e^{i\pi} = -1 \quad\implies\quad e^{i\pi} + 1 = 0$$ - -## Deriving Euler's Identity from Differential Equations - -There is yet another way to establish Euler's formula, using the theory of differential equations. Consider the function: - -$$f(x) = e^{-ix}(\cos x + i\sin x)$$ - -Let us compute its derivative with respect to $x$: - -$$f'(x) = \frac{d}{dx}\left[e^{-ix}(\cos x + i\sin x)\right]$$ - -Applying the product rule: - -$$f'(x) = -ie^{-ix}(\cos x + i\sin x) + e^{-ix}(-\sin x + i\cos x)$$ - -Factor out $e^{-ix}$: - -$$f'(x) = e^{-ix}\left[-i\cos x - i^2\sin x - \sin x + i\cos x\right]$$ - -Since $i^2 = -1$: - -$$f'(x) = e^{-ix}\left[-i\cos x + \sin x - \sin x + i\cos x\right] = e^{-ix}\left[0\right] = 0$$ - -Since the derivative $f'(x) = 0$ for all $x$, the function $f(x)$ must be constant. Evaluating at $x = 0$: - -$$f(0) = e^{0}(\cos 0 + i\sin 0) = 1 \cdot (1 + 0) = 1$$ - -Therefore, for all $x$: - -$$e^{-ix}(\cos x + i\sin x) = 1$$ - -Multiplying both sides by $e^{ix}$ yields Euler's formula: - -$$\cos x + i\sin x = e^{ix}$$ - -Setting $x = \pi$ gives Euler's identity: - -$$e^{i\pi} + 1 = 0$$ - -This differential-equation approach is elegant because it does not require prior knowledge of power series; it only assumes the derivative rules for $\sin$, $\cos$, and $e^x$, plus the fact that a function with zero derivative is constant. - -## Connections to Other Mathematical Concepts - -Euler's identity does not exist in isolation. It sits at the intersection of several deep mathematical ideas: - -### De Moivre's Theorem - -Raising $e^{ix}$ to an integer power $n$ gives: - -$$(e^{ix})^n = e^{inx}$$ - -On the other hand, using Euler's formula: - -$$(\cos x + i\sin x)^n = \cos(nx) + i\sin(nx)$$ - -This is De Moivre's theorem, which provides a compact way to compute powers and roots of complex numbers. - -### Euler's Formula for Polyhedra - -Interestingly, Euler also discovered a celebrated formula in topology: - -$$V - E + F = 2$$ - -where $V$ is the number of vertices, $E$ is the number of edges, and $F$ is the number of faces of a convex polyhedron. While this $V - E + F$ identity is unrelated to $e^{i\pi} + 1 = 0$, both bear his name and demonstrate his extraordinary breadth of insight. - -### The Complex Exponential as a Group Homomorphism - -Viewed abstractly, Euler's formula reveals that the map: - -$$\phi : (\mathbb{R}, +) \to (S^1, \cdot), \quad \phi(x) = e^{ix}$$ - -is a group homomorphism from the additive group of real numbers to the multiplicative group of complex numbers on the unit circle $S^1 = \{z \in \mathbb{C} : |z| = 1\}$. Its kernel is $2\pi\mathbb{Z}$, reflecting the $2\pi$-periodicity of the trigonometric functions. This perspective connects Euler's identity to Fourier analysis, representation theory, and harmonic analysis. - -### Euler's Identity as a Special Case of a General Pattern - -More generally, for any integer $n$: - -$$e^{2\pi i n} = 1$$ - -Euler's identity corresponds to the case $n = \frac{1}{2}$, giving $e^{i\pi} = -1$. This general form underlies the discrete Fourier transform and is fundamental to signal processing, quantum mechanics, and electrical engineering. - -## Why Mathematicians Find It Beautiful - -In a famous 1997 survey published in *The Mathematical Intelligencer*, mathematician David Reimann asked colleagues to name the most beautiful equation in mathematics. Euler's identity received the most votes. Here is why it captivates the mathematical imagination: - -**1. Economy of expression.** In just ten characters — $e^{i\pi} + 1 = 0$ — the equation encapsulates five fundamental constants and three basic arithmetic operations. No other equation achieves this level of compression without sacrificing meaning. - -**2. Unexpected unification.** The constants $e$, $i$, and $\pi$ arise from entirely different areas of mathematics: $e$ from calculus and analysis (growth and logarithms), $i$ from algebra (the solution to $x^2 + 1 = 0$), and $\pi$ from geometry (circles and trigonometry). That they should combine so perfectly was completely unforeseen by Euler himself. - -**3. Depth behind simplicity.** The equation appears trivially simple on its surface, yet its proof connects power series, differential equations, complex analysis, and geometry. As G.H. Hardy wrote, "Beauty is the first test: there is no permanent place in the world for ugly mathematics." - -**4. Existential satisfaction.** The equation asserts that $-1 + 1 = 0$, a statement of perfect balance and closure. It is as if the entire complex plane folds back onto itself, resolving the tension between the real and imaginary worlds into a single point. - -The physicist Richard Feynman called Euler's formula "our jewel" and "the most remarkable formula in mathematics." Whether viewed through the lens of analysis, geometry, or algebra, it reveals a hidden harmony in the structure of numbers. - -## Conclusion - -Euler's identity is far more than a clever coincidence. It is a gateway into the heart of complex analysis, revealing how the exponential function, trigonometric functions, and the geometry of the circle are intimately intertwined. From its foundation in power series, through its geometric interpretation on the complex plane, to its role in modern physics and engineering, the identity $e^{i\pi} + 1 = 0$ stands as a testament to the unity and beauty of mathematics. - -As you contemplate this equation, remember that behind its five symbols lies centuries of human thought, a convergence of ideas from algebra, geometry, and analysis. It reminds us that at the deepest level, mathematics is not a collection of disconnected facts, but a coherent and elegant whole — and Euler's identity is one of its purest expressions. diff --git a/content/posts/hello-world.mdx b/content/posts/hello-world.mdx deleted file mode 100644 index cca53d3..0000000 --- a/content/posts/hello-world.mdx +++ /dev/null @@ -1,525 +0,0 @@ ---- -title: "Mastering TypeScript: From Basics to Advanced Patterns" -date: "2025-01-15" -excerpt: "A comprehensive guide to TypeScript's type system, from basic types to advanced patterns like conditional types, mapped types, and generics." -author: Alex Chen -tags: - - typescript - - programming - - tutorial -coverImage: /images/typescript-cover.jpg ---- - -Welcome to this comprehensive TypeScript tutorial! Whether you are migrating a JavaScript project or starting fresh, this guide will walk you through TypeScript's type system from fundamentals to advanced patterns used in production codebases. - -## 1. Understanding the Type System - -TypeScript's type system is a layer of **static analysis** that sits atop JavaScript. The compiler (`tsc`) performs type checking at build time and emits plain JavaScript that runs in any engine. - -```ts -// Primitive types — the building blocks -let username: string = "alice"; -let age: number = 28; -let isActive: boolean = true; -let tags: string[] = ["typescript", "frontend"]; -let nullable: string | null = null; - -// Type inference — TypeScript can often guess types for you -const port = 3000; // inferred as `number` -const config = { host: "localhost", port: 8080 }; // inferred as `{ host: string; port: number }` - -// The `unknown` vs `any` distinction — critical for type safety -let value: unknown = 42; -// value.toPrecision(3); // ❌ Error: Property 'toPrecision' does not exist on 'unknown' - -if (typeof value === "number") { - console.log(value.toPrecision(3)); // ✅ Safe after narrowing -} -``` - -> **Key principle:** Prefer `unknown` over `any`. `any` disables all type checking, while `unknown` forces you to perform a runtime check before using the value. - -## 2. Interfaces, Types, and Object Shapes - -TypeScript offers two primary ways to define object structures: `interface` and `type`. Understanding when to use each is essential for clean codebases. - -```ts -// --- Interface: best for object contracts (open extension via declaration merging) --- -interface BaseConfig { - debug: boolean; - logLevel: "info" | "warn" | "error"; -} - -// Declaration merging: multiple declarations are combined -interface BaseConfig { - retryCount: number; -} - -// --- Type alias: better for unions, intersections, and mapped types --- -type Status = "idle" | "loading" | "success" | "error"; - -// Intersection types — composing types -type AdminConfig = BaseConfig & { - permissions: string[]; -}; - -// --- Practical example: a fully-typed service layer --- -interface DatabaseClient { - connect(): Promise; - disconnect(): Promise; - query(sql: string, params?: unknown[]): Promise; -} - -type GetUserById = (id: number) => Promise<{ id: number; name: string } | null>; - -async function createUserClient(dsn: string): Promise { - const connection = await fetch(`https://${dsn}/connect`); - if (!connection.ok) throw new Error(`Connection failed: ${connection.status}`); - - return { - async connect() { - await connection.json(); - }, - async disconnect() { - await connection.json(); - }, - async query(sql: string, params: unknown[] = []) { - const response = await connection.clone().json(); - return response.rows as T[]; - }, - }; -} -``` - -## 3. Generics — Writing Reusable, Type-Safe Code - -Generics let you write functions and classes that work with multiple types while preserving type information. They are the backbone of reusable TypeScript libraries. - -```ts -// --- Generic function: identity with preserved type --- -function identity(value: T): T { - return value; -} - -const num = identity(42); // num has type `number` -const str = identity("hi"); // str has type `string` - -// --- Constrained generics: limiting what types are accepted --- -interface HasId { - id: number; -} - -function findFirst(items: T[], predicate: (item: T) => boolean): T | undefined { - return items.find(predicate); -} - -// --- Generic type with default — useful for API client patterns --- -type ApiResponse = - | { status: "success"; data: TData } - | { status: "error"; error: TError }; - -type UserApiResponse = ApiResponse<{ id: number; name: string }>; - -// --- Real-world: a typed event emitter --- -type EventMap = { - click: { x: number; y: number }; - keydown: { key: string; code: string }; - load: { url: string }; -}; - -class TypedEmitter> { - private handlers = new Map void>>(); - - on(event: K, handler: (payload: Map[K]) => void): void { - const key = event as string; - if (!this.handlers.has(key)) { - this.handlers.set(key, []); - } - this.handlers.get(key)!.push(handler as (payload: unknown) => void); - } - - emit(event: K, payload: Map[K]): void { - this.handlers.get(event as string)?.forEach((h) => h(payload)); - } -} - -// Usage — type safety for every event -const emitter = new TypedEmitter(); - -emitter.on("click", (payload) => { - // payload is { x: number; y: number } — auto-completion works! - console.log(`Clicked at ${payload.x}, ${payload.y}`); -}); - -emitter.on("keydown", (payload) => { - // payload is { key: string; code: string } - if (payload.key === "Escape") { - console.log("User pressed Escape"); - } -}); -``` - -## 4. Utility Types — Building Blocks for Complex Schemas - -TypeScript ships with a rich set of built-in utility types that transform existing types. Mastering these is essential for writing maintainable type definitions. - -```ts -interface Article { - id: number; - title: string; - body: string; - tags: string[]; - publishedAt: Date; - authorId: number; -} - -// Partial — make all properties optional -type ArticleInput = Partial
; - -const draft: ArticleInput = { title: "Hello World" }; // id, body, etc. are all optional - -// Required — make all properties required -function upsertArticle(input: Required
): void { - console.log(`Upserting article: ${input.title}`); -} - -// Pick — select a subset of properties -type ArticleSummary = Pick; - -const summary: ArticleSummary = { - id: 1, - title: "TypeScript Basics", - publishedAt: new Date(), -}; - -// Omit — exclude certain properties -type ArticlePreview = Omit; -// Useful: body is too large to send in a list response - -// Record — map-like type -const statusMessages: Record = { - idle: "No request in progress", - loading: "Fetching data...", - success: "Data loaded", - error: "Request failed", -}; - -// Readonly — make all properties immutable -interface Config { - host: string; - port: number; -} - -const config: Readonly = { host: "localhost", port: 3000 }; -// config.port = 8080; // ❌ Error: Cannot assign to 'port' because it is a read-only property. - -// --- Advanced: extracting types from values --- -const API_ENDPOINTS = { - users: "/api/users", - articles: "/api/articles", - comments: "/api/comments", -} as const; - -// Type is `"users" | "articles" | "comments"` -type Endpoint = keyof typeof API_ENDPOINTS; - -// Type is `"/api/users" | "/api/articles" | "/api/comments"` -type EndpointUrl = (typeof API_ENDPOINTS)[keyof typeof API_ENDPOINTS]; - -// --- Extract and Exclude type utilities --- -type NumericFields = Extract; -// NumericFields is "id" | "authorId" — only string keys that are numbers? No, extracted by assignment. - -// Using Exclude to remove unwanted keys -type NonNullableFields = Exclude; -``` - -## 5. Type Guards and Runtime Narrowing - -TypeScript narrows types at compile time based on runtime checks. This bridges the gap between static types and dynamic runtime behavior. - -```ts -// --- typeof type guards --- -function formatValue(value: string | number): string { - if (typeof value === "number") { - return value.toFixed(2); - } - return value.toUpperCase(); -} - -// --- in type guard --- -interface Cat { - meow(): void; - furColor: string; -} - -interface Dog { - bark(): void; - breed: string; -} - -function describePet(pet: Cat | Dog): string { - if ("meow" in pet) { - // TypeScript knows pet is Cat here - return `A ${pet.furColor} cat says meow!`; - } - // Here pet is Dog - return `A ${pet.breed} dog says woof!`; -} - -// --- Custom type guard — reusable predicate --- -function isUser(obj: unknown): obj is { id: number; name: string; email: string } { - return ( - typeof obj === "object" && - obj !== null && - "id" in obj && - "name" in obj && - "email" in obj && - typeof (obj as any).id === "number" && - typeof (obj as any).name === "string" && - typeof (obj as any).email === "string" - ); -} - -// --- Discriminated unions — the most powerful narrowing pattern --- -type NetworkResult = - | { status: "pending"; timestamp: number } - | { status: "success"; data: T; timestamp: number } - | { status: "error"; error: { code: number; message: string }; timestamp: number }; - -function renderResult(result: NetworkResult): string { - switch (result.status) { - case "pending": - return `Loading since ${result.timestamp}`; - case "success": - return `Got ${result.data as unknown as string}`; - case "error": - return `Error ${result.error.code}: ${result.error.message}`; - } -} - -// Exhaustiveness checking — catch unhandled cases at compile time -function assertNever(value: never): never { - throw new Error(`Unexpected value: ${JSON.stringify(value)}`); -} - -function exhaustiveCheck(result: NetworkResult): never { - return assertNever(result); // ❌ Compile error if a case is missing! -} -``` - -## 6. Advanced Patterns: Decorators and Conditional Types - -These advanced patterns appear frequently in enterprise TypeScript codebases. Let's explore practical uses for both. - -```ts -// --- Decorators (experimental syntax, widely used in NestJS, Angular) --- -function LogExecution(target: Object, propertyKey: string, descriptor: PropertyDescriptor) { - const originalMethod = descriptor.value; - - descriptor.value = async function (...args: unknown[]) { - const start = performance.now(); - console.log(`[LOG] ${propertyKey} called with args:`, args); - - try { - const result = await originalMethod.apply(this, args); - const elapsed = performance.now() - start; - console.log(`[LOG] ${propertyKey} completed in ${elapsed.toFixed(2)}ms`); - return result; - } catch (error) { - console.error(`[LOG] ${propertyKey} threw:`, error); - throw error; - } - }; - - return descriptor; -} - -class DataService { - @LogExecution - async fetchData(url: string): Promise> { - const response = await fetch(url); - return response.json(); - } -} - -// --- Conditional types — types that depend on other types --- -type Flattened = T extends Array ? U : T; - -type A = Flattened; // number -type B = Flattened; // string -type C = Flattened; // boolean - -// --- Conditional with `infer` in multiple positions --- -type UnwrapPromise = T extends Promise ? U : T; - -type MaybePromise = T extends Promise | PromiseLike - ? Promise> - : Promise; - -// --- Brand types for runtime type discrimination --- -interface UserId { - _tag: "UserId"; - value: number; -} - -interface ArticleId { - _tag: "ArticleId"; - value: number; -} - -// These are NOT interchangeable despite both being `{ value: number }` -const userId: UserId = { _tag: "UserId", value: 42 }; -// const articleId: ArticleId = userId; // ❌ Error! - -// Factory functions with branded return types -function createUserId(id: number): UserId { - if (id <= 0) throw new Error("Invalid user ID"); - return { _tag: "UserId", value: id }; -} - -function createArticleId(id: number): ArticleId { - if (id <= 0) throw new Error("Invalid article ID"); - return { _tag: "ArticleId", value: id }; -} -``` - -## 7. Error Handling Best Practices - -A robust error handling strategy improves debugging and makes failures explicit in the type system. - -```ts -// --- Result type pattern — explicit error handling without exceptions --- -type Result = - | { ok: true; value: T } - | { ok: false; error: E }; - -function safeParseJson(input: string): Result { - try { - return { ok: true, value: JSON.parse(input) }; - } catch (error) { - return { - ok: false, - error: error instanceof Error ? error : new Error(String(error)), - }; - } -} - -function chainResults( - result: Result, - fn: (value: T) => Result -): Result { - if (!result.ok) return { ok: false, error: result.error }; - return fn(result.value); -} - -// Usage — no try/catch needed at call sites -const jsonResult = safeParseJson('{"name":"Alice"}'); -const parsed = chainResults(jsonResult, (value) => ({ - ok: true, - value: (value as Record).name as string, -})); - -// --- Custom error classes with named constructors --- -class AppError extends Error { - constructor( - public readonly code: string, - message: string, - public readonly metadata?: Record - ) { - super(message); - this.name = "AppError"; - } -} - -class ValidationError extends AppError { - constructor( - field: string, - message: string, - metadata?: Record - ) { - super("VALIDATION_ERROR", `${field}: ${message}`, { field, ...metadata }); - this.name = "ValidationError"; - } -} - -class NotFoundError extends AppError { - constructor(resource: string, id: string) { - super("NOT_FOUND", `${resource} with id "${id}" not found`, { resource, id }); - this.name = "NotFoundError"; - } -} - -// --- Centralized error handler --- -function handleError(error: unknown): { status: number; message: string } { - if (error instanceof AppError) { - switch (error.code) { - case "VALIDATION_ERROR": - return { status: 400, message: error.message }; - case "NOT_FOUND": - return { status: 404, message: error.message }; - default: - return { status: 500, message: "An unexpected error occurred" }; - } - } - - if (error instanceof Error) { - return { status: 500, message: error.message }; - } - - return { status: 500, message: "Unknown error" }; -} -``` - -## 8. Configuration and Build Setup - -A proper TypeScript project needs thoughtful configuration. Here is a production-ready `tsconfig.json` pattern: - -```json -{ - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "lib": ["ES2022"], - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noImplicitReturns": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "declaration": true, - "sourceMap": true - }, - "include": ["src/**/*.ts"], - "exclude": ["node_modules", "dist"] -} -``` - -Key flags explained: -1. `strict: true` — enables all strict type checking options at once -2. `noImplicitAny: true` — disallows implicit `any` types -3. `strictNullChecks: true` — `null` and `undefined` are distinct types -4. `noUnusedLocals` / `noUnusedParameters` — catch dead code at compile time -5. `declaration: true` — emits `.d.ts` files for library consumers - -## Next Steps - -You now have a solid foundation in TypeScript's type system. To continue your journey, here are some recommended directions: - -- **Deep dive into generics** — explore higher-kinded types and type-level programming -- **Learn about module resolution** — understand `--moduleResolution` strategies (`NodeNext` vs `Node16` vs `Classic`) -- **Explore testing with TypeScript** — type-safe assertions with libraries like `vitest` and `tsd` -- **Study architectural patterns** — dependency injection, CQRS, and hexagonal architecture in TypeScript -- **Contribute to type definitions** — help maintain `@types/*` packages on DefinitelyTyped - -TypeScript is a journey, not a destination. The type system rewards patience and pays dividends in developer confidence and reduced bug rates. Start small, enforce `strict` mode early, and let the compiler be your guide. - -Happy coding! 🚀 diff --git a/content/posts/hybrid-post.mdx b/content/posts/hybrid-post.mdx deleted file mode 100644 index 8f58f44..0000000 --- a/content/posts/hybrid-post.mdx +++ /dev/null @@ -1,724 +0,0 @@ ---- -title: "Fourier Analysis: From Mathematical Theory to Python Practice" -date: "2025-03-10" -excerpt: "A deep, hands-on exploration of Fourier analysis — deriving the theory, implementing it in Python, visualizing convergence, and confronting the Gibbs phenomenon head-on." -tags: - - mathematics - - python - - signal-processing - - tutorial -author: James Liu -coverImage: /images/fourier-cover.jpg ---- - -## Fourier Analysis: From Mathematical Theory to Python Practice - -Fourier analysis is one of the most beautiful and practical ideas in all of mathematics. It tells us that *any* sufficiently well-behaved periodic function can be expressed as an infinite sum of simple sines and cosines. This seemingly abstract claim has profound implications — from solving partial differential equations and compressing audio files to reconstructing medical images in MRI machines. - -In this post, we will walk through the full mathematical theory of Fourier series, implement every key formula in Python, and build visualizations that bring the abstractions to life. By the end, you will not only understand *what* a Fourier series is but also *how* to compute one from scratch, *why* convergence behaves the way it does, and *where* the theory meets the messy reality of the Gibbs phenomenon. - -## 1. The Mathematical Foundation - -Let $f: \mathbb{R} \to \mathbb{C}$ be a piecewise continuous function with period $2\pi$, i.e., $f(x + 2\pi) = f(x)$ for all $x \in \mathbb{R}$. The **Fourier series** of $f$ is the formal expansion - -$$ -f(x) \sim \frac{a_0}{2} + \sum_{n=1}^{\infty} \bigl( a_n \cos(nx) + b_n \sin(nx) \bigr), -$$ - -where the Fourier coefficients are defined by the inner products - -$$ -a_n = \frac{1}{\pi} \int_{-\pi}^{\pi} f(x) \cos(nx)\,dx \quad \text{for } n = 0, 1, 2, \dots -$$ - -$$ -b_n = \frac{1}{\pi} \int_{-\pi}^{\pi} f(x) \sin(nx)\,dx \quad \text{for } n = 1, 2, 3, \dots -$$ - -The choice of the factor $\frac{a_0}{2}$ (rather than simply $a_0$) ensures that the formula for $a_n$ holds uniformly for all $n \geq 0$. - -### 1.1 The Complex Form - -It is often more convenient to work with complex exponentials. Using Euler's formula $e^{inx} = \cos(nx) + i\sin(nx)$, we can rewrite the series as - -$$ -f(x) \sim \sum_{n=-\infty}^{\infty} c_n e^{inx}, -$$ - -where the complex Fourier coefficients are - -$$ -c_n = \frac{1}{2\pi} \int_{-\pi}^{\pi} f(x) e^{-inx}\,dx \quad \text{for } n \in \mathbb{Z}. -$$ - -The relationship between the real and complex forms is - -$$ -c_0 = \frac{a_0}{2}, \qquad c_n = \frac{a_n - i\,b_n}{2} \text{ for } n > 0, \qquad c_{-n} = \frac{a_n + i\,b_n}{2} \text{ for } n > 0. -$$ - -### 1.2 Convergence Theorems - -The central question of Fourier analysis is: **when does the Fourier series converge to $f(x)$?** The answer depends on the regularity of $f$. - -**Dirichlet's Theorem.** Suppose $f$ is $2\pi$-periodic and satisfies: - -1. $f$ is absolutely integrable on $[-\pi, \pi]$, i.e., $\int_{-\pi}^{\pi} |f(x)|\,dx < \infty$; -2. $f$ has a finite number of maxima and minima in any period; -3. $f$ has at most a finite number of discontinuities in any period, and each is a jump discontinuity. - -Then the Fourier series of $f$ converges to $\frac{f(x^+) + f(x^-)}{2}$ at every point $x$, where $f(x^+)$ and $f(x^-)$ denote the right and left limits, respectively. At points of continuity, the series converges to $f(x)$ itself. - -**Parseval's Identity.** If $f$ is square-integrable on $[-\pi, \pi]$, then - -$$ -\frac{1}{\pi} \int_{-\pi}^{\pi} |f(x)|^2\,dx = \frac{a_0^2}{2} + \sum_{n=1}^{\infty} \bigl( a_n^2 + b_n^2 \bigr) = \sum_{n=-\infty}^{\infty} |c_n|^2. -$$ - -Parseval's identity expresses the conservation of energy: the total energy of the signal in the time domain equals the total energy of its frequency components. - -## 2. Worked Example: The Square Wave - -As our canonical example, consider the $2\pi$-periodic square wave defined by - -$$ -f(x) = \begin{cases} --1 & \text{for } -\pi < x < 0,\\[4pt] -\phantom{-}1 & \text{for } 0 < x < \pi, -\end{cases} -\qquad f(0) = f(\pm \pi) = 0. -$$ - -This is an **odd** function, so all cosine coefficients vanish: $a_n = 0$ for every $n \geq 0$. We need only compute the sine coefficients: - -$$ -b_n = \frac{1}{\pi} \int_{-\pi}^{\pi} f(x) \sin(nx)\,dx -= \frac{2}{\pi} \int_{0}^{\pi} \sin(nx)\,dx -= \frac{2}{\pi} \left[ -\frac{\cos(nx)}{n} \right]_{0}^{\pi} -= \frac{2}{\pi} \cdot \frac{1 - (-1)^n}{n}. -$$ - -For even $n$, we get $b_n = 0$. For odd $n = 2k+1$ ($k = 0, 1, 2, \dots$), - -$$ -b_{2k+1} = \frac{4}{(2k+1)\pi}. -$$ - -Thus the Fourier series of the square wave is the celebrated result - -$$ -f(x) \sim \frac{4}{\pi} \sum_{k=0}^{\infty} \frac{\sin\bigl((2k+1)x\bigr)}{2k+1} -= \frac{4}{\pi}\left(\sin x + \frac{\sin 3x}{3} + \frac{\sin 5x}{5} + \cdots\right). -$$ - -Notice the striking pattern: only **odd harmonics** appear, and the amplitudes decay as $1/n$. This slow decay is a direct consequence of the discontinuities in $f$. By comparison, a smooth function like $\cos^3(x)$ would have coefficients that decay exponentially. - -### 2.1 Numerical Verification in Python - -Let's verify the coefficient formulas numerically. We integrate directly using `scipy.integrate.quad` and compare against the analytical values. - -```python -""" -Verify Fourier coefficients of a square wave via numerical integration. -Compares computed coefficients against the known analytical formulas. -""" - -import numpy as np -from scipy.integrate import quad - -def square_wave(x): - """2π-periodic square wave: -1 on (-π, 0), +1 on (0, π).""" - return np.where(np.sin(x) >= 0, 1.0, -1.0) - -def analytical_an(n): - """Analytical cosine coefficients (all zero for the square wave).""" - return 0.0 - -def analytical_bn(n): - """Analytical sine coefficients: 4/(nπ) for odd n, 0 for even n.""" - if n % 2 == 0: - return 0.0 - return 4.0 / (n * np.pi) - -# Compute coefficients numerically for n = 0..7 -for n in range(8): - a_n, _ = quad(lambda x: square_wave(x) * np.cos(n * x), -np.pi, np.pi) - a_n /= np.pi - - if n == 0: - print(f"a_{n:2d}: numerical = {a_n:+.10f}, analytical = N/A (DC offset)") - continue - - b_n, _ = quad(lambda x: square_wave(x) * np.sin(n * x), -np.pi, np.pi) - b_n /= np.pi - - b_exact = analytical_bn(n) - print(f"a_{n:2d}: numerical = {a_n:+.10f}, analytical = {analytical_an(n):+.10f}") - print(f"b_{n:2d}: numerical = {b_n:+.10f}, analytical = {b_exact:+.10f}") -``` - -The output confirms that $a_n \approx 0$ for all $n$ and $b_n \approx 4/(n\pi)$ for odd $n$, with errors on the order of machine precision. - -## 3. Building a Fourier Synthesizer in Python - -Now let's implement a general-purpose Fourier series computation and synthesis engine. We'll work with the complex form, which is both cleaner and directly compatible with the Fast Fourier Transform. - -```python -""" -A complete Fourier series synthesizer. -Computes coefficients, reconstructs the signal, and plots the result. -""" - -import numpy as np -import matplotlib.pyplot as plt -from scipy.integrate import quad - -class FourierSeries: - """Compute and evaluate the Fourier series of a periodic function.""" - - def __init__(self, f, period=2 * np.pi, a=None, b=None): - """ - Parameters - ---------- - f : callable - The periodic function f(x). - period : float - The fundamental period 2L (default 2π). - a, b : callable, optional - Pre-computed coefficient functions. If not given, - coefficients are computed numerically on demand. - """ - self.f = f - self.period = period - self.L = period / 2.0 - self._a = a - self._b = b - self._cache = {} - - def a_n(self, n): - """Compute the n-th cosine coefficient numerically.""" - if self._a is not None: - return self._a(n) - key = ('a', n) - if key in self._cache: - return self._cache[key] - integrand = lambda x: self.f(x) * np.cos(n * np.pi * x / self.L) - val, _ = quad(integrand, -self.L, self.L) - self._cache[key] = val / self.L - return self._cache[key] - - def b_n(self, n): - """Compute the n-th sine coefficient numerically.""" - if self._b is not None: - return self._b(n) - key = ('b', n) - if key in self._cache: - return self._cache[key] - integrand = lambda x: self.f(x) * np.sin(n * np.pi * x / self.L) - val, _ = quad(integrand, -self.L, self.L) - self._cache[key] = val / self.L - return self._cache[key] - - def partial_sum(self, x, N): - """ - Evaluate the partial sum S_N(x) = a_0/2 + sum_{n=1}^{N} (a_n cos(nx) + b_n sin(nx)). - """ - x = np.asarray(x) - result = np.zeros_like(x, dtype=float) - result += self.a_n(0) / 2.0 - for n in range(1, N + 1): - result += self.a_n(n) * np.cos(n * np.pi * x / self.L) - result += self.b_n(n) * np.sin(n * np.pi * x / self.L) - return result - - def complex_c_n(self, n): - """Compute the n-th complex Fourier coefficient c_n.""" - key = ('c', n) - if key in self._cache: - return self._cache[key] - integrand = lambda x: self.f(x) * np.exp(-1j * n * np.pi * x / self.L) - val, _ = quad(integrand, -self.L, self.L) - self._cache[key] = val / self.L - return self._cache[key] - - def partial_sum_complex(self, x, N): - """Evaluate the complex partial sum sum_{n=-N}^{N} c_n e^{i n π x / L}.""" - x = np.asarray(x) - result = np.zeros_like(x, dtype=complex) - for n in range(-N, N + 1): - result += self.complex_c_n(n) * np.exp(1j * n * np.pi * x / self.L) - return result.real - - -# --- Demonstration: Triangle Wave --- -def triangle_wave(x): - """2π-periodic triangle wave oscillating between -1 and 1.""" - x_mod = np.mod(x + np.pi, 2 * np.pi) - np.pi - return np.where(x_mod < 0, -x_mod / np.pi, (2 * np.pi - x_mod) / np.pi - 1) * 2 - - -fs = FourierSeries(triangle_wave, period=2 * np.pi) -x = np.linspace(-np.pi, np.pi, 2000) - -plt.figure(figsize=(12, 6)) -plt.plot(x, triangle_wave(x), 'k--', linewidth=2, label='Original triangle wave') - -for N in [3, 7, 15, 31]: - y = fs.partial_sum(x, N) - plt.plot(x, y, linewidth=1.5, label=f'$S_{{{N}}}(x)$') - -plt.legend(fontsize=12) -plt.title('Fourier Series Convergence: Triangle Wave', fontsize=14) -plt.xlabel('$x$') -plt.ylabel('$f(x)$') -plt.grid(True, alpha=0.3) -plt.tight_layout() -plt.savefig('triangle_wave_fourier.png', dpi=150) -``` - -The triangle wave converges much faster than the square wave — its coefficients decay as $1/n^2$ because it is continuous (though not differentiable) everywhere. - -## 4. The Fast Fourier Transform: From Theory to Algorithm - -Computing Fourier coefficients numerically via quadrature works fine for a handful of coefficients, but becomes prohibitively slow for large $N$. The **Discrete Fourier Transform (DFT)** provides the correct framework for sampled data, and the **Fast Fourier Transform (FFT)** computes it in $O(N \log N)$ time rather than $O(N^2)$. - -Given $N$ equally-spaced samples $f_j = f(x_j)$ where $x_j = j \cdot \frac{2\pi}{N}$ for $j = 0, 1, \dots, N-1$, the DFT is defined as - -$$ -\hat{f}_k = \sum_{j=0}^{N-1} f_j \, e^{-2\pi i j k / N} \qquad \text{for } k = 0, 1, \dots, N-1. -$$ - -The inverse DFT recovers the samples: - -$$ -f_j = \frac{1}{N} \sum_{k=0}^{N-1} \hat{f}_k \, e^{2\pi i j k / N}. -$$ - -For a real-valued signal of length $N$, the FFT yields $N$ complex coefficients. The first $N/2$ coefficients correspond to frequencies $0, 1, 2, \dots, N/2 - 1$ (non-negative frequencies), while the remaining $N/2$ coefficients correspond to negative frequencies $-N/2, -N/2 + 1, \dots, -1$. - -```python -""" -Demonstrate the FFT: compare the numpy FFT output against our -manual Fourier series computation for the square wave. -""" - -import numpy as np -import matplotlib.pyplot as plt - -def square_wave(x): - """2π-periodic square wave.""" - return np.where((x % (2 * np.pi)) >= np.pi, 1.0, -1.0) - -# Sample parameters -N = 256 # number of samples -x = np.linspace(0, 2 * np.pi, N, endpoint=False) -f_samples = square_wave(x) - -# Compute FFT -f_hat = np.fft.fft(f_samples) -freqs = np.fft.fftfreq(N, d=2 * np.pi / N) - -# The FFT coefficient f_hat[k] relates to the complex Fourier coefficient c_n by: -# c_n = f_hat[k] / N -# where n = k for k < N/2, and n = k - N for k >= N/2 - -N_terms = 20 -amplitudes = [] -harmonics = [] - -for k in range(1, N_terms + 1): - c_n = f_hat[k] / N - amplitude = 2 * np.abs(c_n) # factor of 2 because we fold negative freqs - harmonics.append(k) - amplitudes.append(amplitude) - -fig, ax = plt.subplots(2, 1, figsize=(12, 8)) - -# Time-domain signal -ax[0].plot(x, f_samples, 'b-', linewidth=1.5, label='Sampled square wave') -ax[0].set_xlabel('$x$') -ax[0].set_ylabel('$f(x)$') -ax[0].set_title('Time Domain: Square Wave Samples (N = {})'.format(N)) -ax[0].grid(True, alpha=0.3) - -# Frequency spectrum -ax[1].stem(harmonics, amplitudes, basefmt='k-', use_line_collection=True) -ax[1].set_xlabel('Harmonic number $n$') -ax[1].set_ylabel('Amplitude $|c_n|$') -ax[1].set_title('Frequency Spectrum: Only Odd Harmonics Present') -ax[1].set_xticks(np.arange(0, N_terms + 1, 2)) -ax[1].grid(True, alpha=0.3) - -plt.tight_layout() -plt.savefig('fft_spectrum.png', dpi=150) -``` - -This visualization clearly shows that only odd harmonics carry energy — the even harmonics have zero amplitude, exactly as our analytical derivation predicted. - -## 5. Visualization: Watching Convergence in Action - -One of the most rewarding aspects of Fourier analysis is *seeing* convergence happen. The following script builds an animation-style comparison: we plot the partial sums $S_N(x)$ for increasing values of $N$ alongside the target function, revealing how the approximation improves and how the Gibbs phenomenon manifests at the discontinuities. - -```python -""" -Visualization script: partial sums of the square wave Fourier series. -Plays through increasing N to show convergence behavior and the Gibbs phenomenon. -""" - -import numpy as np -import matplotlib.pyplot as plt - -def fourier_square_wave(x, N_terms): - """ - Compute the N_terms-term partial sum of the square wave Fourier series. - Only odd harmonics contribute: 4/(nπ) sin(nx) for n = 1, 3, 5, ... - """ - result = np.zeros_like(x, dtype=float) - for n in range(1, N_terms + 1, 2): - result += (4.0 / (n * np.pi)) * np.sin(n * x) - return result - -def square_wave(x): - """Ideal square wave for reference.""" - x_mod = np.mod(x + np.pi, 2 * np.pi) - np.pi - return np.where(x_mod >= 0, 1.0, -1.0) - -x = np.linspace(-np.pi, np.pi, 2000) -ideal = square_wave(x) - -# Partial sum amplitudes to visualize -N_values = [1, 3, 5, 10, 20, 50, 100] - -fig, axes = plt.subplots(3, 3, figsize=(18, 14)) -axes = axes.flatten() - -colors = plt.cm.viridis(np.linspace(0, 1, len(N_values))) - -for idx, N in enumerate(N_values): - approx = fourier_square_wave(x, N) - ax = axes[idx] - - ax.plot(x, ideal, 'k--', linewidth=2, label='Ideal square wave', zorder=1) - ax.plot(x, approx, color=colors[idx], linewidth=1.5, - label=f'$S_{{{N}}}(x)$', zorder=2) - - # Mark the overshoot near x = 0 - overshoot_idx = np.argmax(approx[1:500]) + 1 - overshoot_val = approx[overshoot_idx] - ax.plot(x[overshoot_idx], overshoot_val, 'ro', markersize=6, - label=f'Overshoot: {overshoot_val:.4f}', zorder=3) - - ax.set_xlim(-np.pi, np.pi) - ax.set_ylim(-1.5, 1.5) - ax.set_title(f'{N} Harmonic(s) — Overshoot = {overshoot_val:.4f}', fontsize=11) - ax.set_xlabel('$x$') - ax.set_ylabel('$f(x)$') - ax.grid(True, alpha=0.3) - ax.legend(fontsize=8) - -# Hide the last unused subplot -axes[-1].set_visible(False) - -fig.suptitle('Fourier Series Convergence for the Square Wave:\n' - 'The overshoot near discontinuities does NOT vanish — this is the Gibbs phenomenon', - fontsize=16, fontweight='bold', y=1.02) - -plt.tight_layout() -plt.savefig('fourier_convergence.png', dpi=150, bbox_inches='tight') -print("Visualization saved to fourier_convergence.png") -``` - -Key observations from the output: - -1. **Near $x = 0$**: The approximation improves rapidly in the *bulk* of the interval, but a persistent overshoot of approximately $0.179$ (about 9% of the jump) remains near the discontinuity. -2. **As $N \to \infty$**: The overshoot region narrows, but its peak height converges to a constant. The area under the overshoot shrinks (the $L^2$ error goes to zero), but the pointwise maximum error does not. -3. **Smooth regions**: Away from discontinuities, the convergence is rapid and uniform. By $N = 20$, the approximation is visually indistinguishable from the ideal. - -## 6. The Gibbs Phenomenon: Theory Meets Reality - -The **Gibbs phenomenon**, discovered by Henry Wilbraham in 1848 and later popularized by J. Willard Gibbs in 1899, describes the persistent overshoot that occurs when approximating a discontinuous function by its Fourier partial sums. - -### 6.1 The Gibbs Constant - -Let $f$ be a function with a jump discontinuity at $x_0$ of size $J = f(x_0^+) - f(x_0^-)$. The $N$-th partial sum $S_N(x)$ of the Fourier series satisfies - -$$ -\lim_{N \to \infty} \sup_{x} \bigl(S_N(x) - f(x)\bigr) \approx J \cdot \frac{S_G}{2}, -$$ - -where $S_G$ is the **Gibbs constant**: - -$$ -S_G = \frac{2}{\pi} \int_{0}^{\pi} \frac{\sin t}{t}\,dt \approx 1.17898 \approx 1 + \frac{1}{\pi} \operatorname{Si}(\pi). -$$ - -Here, $\operatorname{Si}(x) = \int_0^x \frac{\sin t}{t}\,dt$ is the sine integral. The overshoot is approximately - -$$ -\text{Overshoot} \approx J \times \frac{S_G - 1}{2} \approx J \times 0.08949 \approx 9\% \text{ of the jump}. -$$ - -This is *not* a computational artifact — it is a fundamental property of Fourier series and of any approximation by global basis functions. - -### 6.2 Numerical Confirmation - -```python -""" -Quantitative verification of the Gibbs constant. -Measures the overshoot height for increasing N and confirms convergence -to the theoretical Gibbs constant value. -""" - -import numpy as np - -def fourier_square_wave(x, N_terms): - """Partial sum with only odd harmonics.""" - result = np.zeros_like(x, dtype=float) - for n in range(1, N_terms + 1, 2): - result += (4.0 / (n * np.pi)) * np.sin(n * x) - return result - -# Scan near the discontinuity at x = 0 to find the maximum overshoot -x_fine = np.linspace(0, np.pi, 100000) - -print("Gibbs Phenomenon: Overshoot Analysis") -print("=" * 55) -print(f"{'N':>6s} {'Max Value':>12s} {'Overshoot':>12s} {'Ratio':>8s}") -print("-" * 55) - -theoretical_gibbs = 1.089489690436 -target = 1.0 # The square wave goes to +1 on (0, π) - -for N in [10, 50, 100, 500, 1000, 5000]: - approx = fourier_square_wave(x_fine, N) - max_val = np.max(approx) - overshoot = max_val - target - ratio = max_val / theoretical_gibbs - - print(f"{N:>6d} {max_val:>12.8f} {overshoot:>12.8f} {ratio:>8.6f}") - -print() -print(f"Theoretical Gibbs limit: {theoretical_gibbs:.10f}") -print("As N increases, the overshoot height converges to this value.") -``` - -The output converges toward $1.08949$ from below, confirming the theory. Note that increasing $N$ further narrows the *width* of the overshoot region while keeping its *height* nearly constant. - -## 7. Beyond Fourier Series: The Continuous Transform - -Fourier series apply to periodic functions. For non-periodic, finite-energy signals, we turn to the **Fourier transform**. If $f \in L^1(\mathbb{R})$, the Fourier transform is defined as - -$$ -\hat{f}(\omega) = \int_{-\infty}^{\infty} f(t) \, e^{-i\omega t}\,dt, -$$ - -and the inverse transform recovers the original function: - -$$ -f(t) = \frac{1}{2\pi} \int_{-\infty}^{\infty} \hat{f}(\omega) \, e^{i\omega t}\,d\omega. -$$ - -A particularly illuminating example: the Fourier transform of a Gaussian is another Gaussian. Let $f(t) = e^{-at^2}$ for $a > 0$. Then - -$$ -\hat{f}(\omega) = \sqrt{\frac{\pi}{a}} \, e^{-\omega^2 / (4a)}. -$$ - -This self-duality is intimately related to the **Heisenberg uncertainty principle** in quantum mechanics. A function and its Fourier transform cannot both be arbitrarily concentrated — the more localized $f$ is in time, the more spread out $\hat{f}$ is in frequency, and vice versa. - -```python -""" -Fourier transform of a Gaussian: numerical verification. -Shows that the FT of a Gaussian is another Gaussian. -""" - -import numpy as np -import matplotlib.pyplot as plt - -# Parameters -a = 1.0 # Gaussian width parameter -t = np.linspace(-5, 5, 1000) - -# Original Gaussian -f_t = np.exp(-a * t**2) - -# Numerical FFT (note: numpy's FFT assumes uniform sampling) -dt = t[1] - t[0] -N = len(t) -f_hat = np.fft.fftshift(np.fft.fft(np.fft.ifftshift(f_t))) * dt -freqs = np.fft.fftshift(np.fft.fftfreq(N, d=dt)) - -# Analytical FT: sqrt(pi/a) * exp(-omega^2 / (4a)) -f_hat_analytical = np.sqrt(np.pi / a) * np.exp(-freqs**2 / (4 * a)) - -fig, ax = plt.subplots(2, 1, figsize=(12, 8)) - -ax[0].plot(t, f_t, 'b-', linewidth=2, label='$f(t) = e^{{-t^2}}$') -ax[0].set_xlabel('$t$') -ax[0].set_ylabel('$f(t)$') -ax[0].set_title('Gaussian in the Time Domain') -ax[0].legend() -ax[0].grid(True, alpha=0.3) - -ax[1].plot(freqs, np.abs(f_hat), 'b-', linewidth=1.5, label='Numerical FFT') -ax[1].plot(freqs, f_hat_analytical, 'r--', linewidth=2, label='$\hat{f}(\omega) = \sqrt{\pi}\,e^{{-\omega^2/4}}$') -ax[1].set_xlabel('$\omega$') -ax[1].set_ylabel('$\hat{f}(\omega)$') -ax[1].set_title('Gaussian in the Frequency Domain — Self-Dual Under Fourier Transform') -ax[1].legend() -ax[1].grid(True, alpha=0.3) - -plt.tight_layout() -plt.savefig('gaussian_fourier.png', dpi=150) -print("Gaussian FT visualization saved.") -``` - -The numerical FFT (blue) overlays perfectly with the analytical Gaussian (red dashed), confirming both the theory and the correctness of our numerical approach. - -## 8. Application: Audio Equalization - -One of the most direct applications of Fourier analysis is audio processing. An equalizer works by transforming an audio signal into the frequency domain, amplifying or attenuating specific frequency bands, and transforming back. - -```python -""" -Simple audio equalizer using FFT. -Reads a WAV file, applies frequency-domain filtering, and writes the result. -""" - -import numpy as np -import matplotlib.pyplot as plt -from scipy.io import wavfile -from scipy.signal import fftconvolve - -def equalize_audio(wav_path, output_path, bass_range=(0, 200), - mid_range=(200, 4000), treble_range=(4000, 20000)): - """ - Apply a three-band equalizer to an audio file. - - Parameters - ---------- - wav_path : str - Input WAV file path. - output_path : str - Output WAV file path. - bass_range, mid_range, treble_range : tuple - Frequency ranges in Hz for each band. - - The equalizer boosts bass by +6 dB, cuts mids by -3 dB, - and boosts treble by +4 dB. - """ - # Read the audio file - sample_rate, audio_data = wavfile.read(wav_path) - - # Convert to float64 and normalize - if audio_data.dtype == np.int16: - audio_data = audio_data.astype(np.float64) / 32768.0 - elif audio_data.dtype == np.int32: - audio_data = audio_data.astype(np.float64) / 2147483648.0 - - # Handle stereo: average channels - if len(audio_data.shape) > 1: - audio_data = audio_data.mean(axis=1) - - N = len(audio_data) - f_hat = np.fft.fft(audio_data) - freqs = np.fft.fftfreq(N, d=1.0 / sample_rate) - - # Create gain curve (linear from dB) - gain = np.ones(N) - - for i, freq in enumerate(freqs): - f_abs = np.abs(freq) - if f_abs < bass_range[1]: - gain[i] = 10.0 ** (6.0 / 20.0) # +6 dB boost - elif f_abs < mid_range[1]: - gain[i] = 10.0 ** (-3.0 / 20.0) # -3 dB cut - elif f_abs <= treble_range[1]: - gain[i] = 10.0 ** (4.0 / 20.0) # +4 dB boost - else: - gain[i] = 1.0 # no change - - # Apply gain in frequency domain - f_hat_eq = f_hat * gain - - # Inverse FFT to get equalized signal - audio_eq = np.fft.ifft(f_hat_eq).real - - # Normalize to prevent clipping - max_val = np.max(np.abs(audio_eq)) - if max_val > 0: - audio_eq /= max_val - - # Write output - audio_eq_int16 = np.int16(audio_eq * 32767) - wavfile.write(output_path, sample_rate, audio_eq_int16) - - # Plot the spectrum before and after - freqs_pos = freqs[:N // 2] - gain_pos = gain[:N // 2] - spec_before = np.abs(f_hat[:N // 2]) - spec_after = np.abs(f_hat_eq[:N // 2]) - - fig, ax = plt.subplots(2, 1, figsize=(14, 8)) - - ax[0].semilogy(freqs_pos, spec_before + 1e-15, 'b-', linewidth=1, - label='Original spectrum') - ax[0].semilogy(freqs_pos, spec_after + 1e-15, 'r-', linewidth=1, - label='Equalized spectrum') - ax[0].axvspan(bass_range[0], bass_range[1], alpha=0.1, color='red') - ax[0].axvspan(mid_range[0], mid_range[1], alpha=0.1, color='green') - ax[0].axvspan(treble_range[0], treble_range[1], alpha=0.1, color='blue') - ax[0].set_xlabel('Frequency (Hz)') - ax[0].set_ylabel('Magnitude') - ax[0].set_title('Spectral Comparison: Before and After Equalization') - ax[0].legend() - ax[0].set_xscale('log') - ax[0].grid(True, alpha=0.3) - - ax[1].plot(freqs_pos, gain_pos[:len(freqs_pos)], 'k-', linewidth=2) - ax[1].set_xlabel('Frequency (Hz)') - ax[1].set_ylabel('Gain (linear)') - ax[1].set_title('Equalizer Gain Curve (Linear Scale)') - ax[1].set_xscale('log') - ax[1].grid(True, alpha=0.3) - - plt.tight_layout() - plt.savefig('equalizer_spectra.png', dpi=150) - print(f"Equalized audio saved to {output_path}") - - -# To run: equalize_audio('input.wav', 'output.wav') -``` - -This script demonstrates the full signal processing pipeline: time-domain → frequency-domain → manipulate → frequency-domain → time-domain. The equalizer operates entirely in the frequency domain, which is computationally efficient and conceptually clean. - -## 9. Connecting Theory to Practice: What the Math Tells Us - -The journey from Dirichlet's convergence theorem to a working audio equalizer reveals a deep unity between abstract mathematics and concrete engineering: - -1. **Convergence rate reflects smoothness.** The square wave's $1/n$ coefficient decay (slow convergence) directly reflects its discontinuities, while the triangle wave's $1/n^2$ decay (fast convergence) reflects its continuity. In general, if $f$ has $k$ continuous derivatives, the coefficients decay as $O(n^{-(k+1)})$. This is why smooth, band-limited signals are ideal for digital processing. - -2. **Parseval's identity is energy conservation.** When we filter or equalize audio, Parseval's identity guarantees that the total energy computed in either the time domain or frequency domain is the same. This is fundamental to signal processing and is the basis for energy-based feature extraction in machine learning. - -3. **The Gibbs phenomenon is a fundamental limitation.** No amount of computational power can eliminate the overshoot — it is a mathematical inevitability of representing discontinuous functions with global, smooth basis functions. In practice, engineers use windowing techniques (Hamming, Hanning, Blackman windows) to mitigate the effect by trading resolution for reduced ringing. - -4. **The uncertainty principle is a theorem, not a philosophy.** The mathematical fact that a function and its Fourier transform cannot both be arbitrarily localized underpins everything from radar pulse design to quantum mechanics. The more precisely you want to know a signal's frequency, the longer you must observe it in time. - -## Conclusion - -Fourier analysis is a magnificent bridge between the abstract and the concrete. The elegant integral formulas of Dirichlet give rise to algorithms that power our smartphones. The Gibbs phenomenon, once a curiosity of 19th-century mathematics, now explains artifacts in image compression and MRI reconstruction. - -The key takeaway is this: the mathematics is not *separate* from the code — it *is* the code. Every line of our synthesizer, every FFT call, every equalizer band is a direct instantiation of a mathematical theorem. Understanding the theory doesn't just help you write better code; it helps you understand what your code is *doing* and *why* it works. - -As you build your own signal processing systems, keep these principles in mind: - -- **Smooth functions converge fast** — design your filters and signals accordingly. -- **Discontinuities always ring** — expect and plan for the Gibbs phenomenon. -- **Energy is conserved across domains** — use Parseval's identity to validate your implementations. -- **Resolution is a trade-off** — time resolution versus frequency resolution is not a limitation; it is a law of nature. - -The beauty of Fourier analysis lies in this harmony: a single mathematical insight — that any signal can be decomposed into frequencies — echoes through physics, engineering, computer science, and beyond. The code you write is the theorem you prove. - ---- - -*All code in this post has been tested with Python 3.10+, NumPy 1.24+, SciPy 1.10+, and Matplotlib 3.7+. The mathematical derivations follow standard references including Rudin's "Real and Complex Analysis" and Bracewell's "The Fourier Transform and Its Applications."* \ No newline at end of file