diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000000..73eec05e9b --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,63 @@ +{ + "permissions": { + "allow": [ + "Bash(git config:*)", + "Bash(claude mcp:*)", + "Bash(cat:*)", + "Bash(find:*)", + "Bash(grep:*)", + "Bash(findstr:*)", + "Bash(npm run:*)", + "Bash(npm run build:*)", + "Bash(npm install)", + "Bash(npm test:*)", + "Bash(npm publish:*)", + "Bash(npm version:*)", + "Bash(npm run lint:*)", + "Bash(npm run format:*)", + "Bash(npm run coverage:*)", + "Bash(npm run typecheck:*)", + "Bash(npm ls:*)", + "Bash(npm whoami:*)", + "Bash(node -e:*)", + "Bash(npx vitest:*)", + "Bash(npx mocha:*)", + "Bash(npx tsc:*)", + "Bash(npx gulp:*)", + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(git push:*)", + "Bash(git reset:*)", + "mcp__memory-mcp__read_graph", + "mcp__memory-mcp__search_nodes", + "mcp__memory-mcp__create_entities", + "mcp__memory-mcp__create_relations", + "mcp__memory-mcp__add_tags", + "mcp__memory-mcp__set_importance", + "mcp__memory-mcp__get_graph_stats", + "mcp__memory-mcp__export_graph", + "mcp__memory-mcp__search_by_date_range", + "mcp__memory-mcp__delete_entities", + "mcp__memory-mcp__add_observations", + "mcp__memory-mcp__delete_observations", + "mcp__memory-mcp__fuzzy_search", + "mcp__deepthinking-mcp__deepthinking", + "mcp__math-mcp__*" + ], + "deny": [], + "ask": [] + }, + "enableAllProjectMcpServers": true, + "enabledMcpjsonServers": [ + "sequential-thinking-mcp", + "everything-mcp", + "fermat-mcp", + "math-mcp", + "playwright", + "substack-mcp", + "time", + "memory-mcp", + "fzf-mcp", + "deepthinking-mcp" + ] +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..ffc8f1d2b6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,527 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Math.js is an extensive math library for JavaScript and Node.js featuring: +- Flexible expression parser with symbolic computation support +- Large set of built-in functions and constants +- Support for multiple data types: numbers, big numbers, complex numbers, fractions, units, matrices +- ES modules codebase requiring all files to have real `.js` extensions +- Currently undergoing TypeScript + WASM + parallel computing refactoring (9% complete) + +## Build Commands + +```bash +# Install dependencies +npm install + +# Full build (ESM, CJS, browser bundle, WASM) +npm run build + +# Clean build output +npm run build:clean + +# Compile TypeScript files +npm run compile:ts + +# Watch TypeScript changes +npm run watch:ts + +# Build WASM modules +npm run build:wasm + +# Build WASM in debug mode +npm run build:wasm:debug + +# Compile JavaScript (without full build) +npm run compile + +# Watch for changes +npm run watch +``` + +## Testing Commands + +```bash +# Run unit tests and lint +npm test + +# Run all tests (unit, generated, node, types) +npm run test:all + +# Run only unit tests (src/) +npm run test:src + +# Run generated code tests +npm run test:generated + +# Run Node.js integration tests +npm run test:node + +# Run TypeScript type tests +npm run test:types + +# Run browser tests (Firefox headless) +npm run test:browser + +# Run on LambdaTest (requires LT_USERNAME and LT_ACCESS_KEY env vars) +npm run test:lambdatest + +# Code coverage with report +npm run coverage +# View report at: ./coverage/lcov-report/index.html +``` + +## Linting and Code Style + +```bash +# Run eslint (also runs with npm test) +npm run lint + +# Auto-fix linting issues +npm run format + +# Validate ASCII characters +npm run validate:ascii +``` + +## Architecture Overview + +### Core Dependency Injection System + +Math.js uses a **factory function + dependency injection** architecture: + +1. **Factory Functions**: Each function is created via an immutable factory function (e.g., `createAdd`, `createMultiply`) +2. **Typed Functions**: All functions use [`typed-function`](https://github.com/josdejong/typed-function/) to support multiple data types and automatic type conversions +3. **Dependency Injection**: Functions declare their dependencies, allowing automatic composition and customization +4. **Instance Creation**: `math.create(factories, config)` creates a MathJS instance from factory functions + +**Example**: If you extend `multiply` to support a new data type `MyDecimal`, functions like `prod` that depend on `multiply` automatically support `MyDecimal` too. + +### Directory Structure + +``` +src/ +├── core/ # Core system (create, config, typed-function) +├── type/ # Data type implementations (Complex, BigNumber, Matrix, etc.) +├── function/ # All mathematical functions organized by category +│ ├── arithmetic/ # Basic arithmetic (add, multiply, etc.) +│ ├── algebra/ # Algebraic functions (derivative, simplify, etc.) +│ ├── matrix/ # Matrix operations +│ ├── trigonometry/ # Trig functions +│ ├── statistics/ # Statistical functions +│ ├── probability/ # Probability functions +│ ├── utils/ # Utility functions (isInteger, typeOf, etc.) +│ └── [other categories] +├── expression/ # Expression parser and evaluation +│ ├── Parser.js # Main expression parser +│ ├── embeddedDocs/ # Documentation for parser functions +│ ├── node/ # AST node types +│ └── transform/ # Expression transformations +├── parallel/ # Parallel computing (WorkerPool, ParallelMatrix) +├── wasm/ # WASM integration layer (WasmLoader, MatrixWasmBridge) +├── factoriesAny.js # All factory functions (full version) +├── factoriesNumber.js # Number-only factory functions (lightweight) +└── defaultInstance.js # Default math.js instance + +src-wasm/ # TypeScript/AssemblyScript WASM modules +├── matrix/ # Matrix operations for WASM +├── algebra/ # Linear algebra for WASM +├── arithmetic/ # Arithmetic operations for WASM +├── trigonometry/ # Trig functions for WASM +├── signal/ # Signal processing (FFT, etc.) +└── index.ts # WASM entry point + +test/ +├── unit-tests/ # Unit tests (main test suite) +├── generated-code-tests/ # Tests for generated entry files +├── node-tests/ # Node.js specific tests +├── typescript-tests/ # TypeScript type definition tests +├── browser-test-config/ # Browser test configurations +└── benchmark/ # Performance benchmarks + +types/ +├── index.d.ts # TypeScript type definitions +└── EXPLANATION.md # Guide for maintaining type definitions + +tools/ # Build and development tools +├── entryGenerator.js # Generates entry point files +├── docgenerator.js # Generates documentation +├── migrate-to-ts.js # TypeScript migration tool +└── validateAsciiChars.js # ASCII validation +``` + +### TypeScript + WASM Refactoring (In Progress) + +The codebase is being gradually converted to TypeScript with WASM support: + +- **Status**: 61/673 files converted (9%) +- **Goal**: Type safety, 2-25x performance improvements, multi-core support +- **Strategy**: Incremental conversion, 100% backward compatible +- **See**: `README_TYPESCRIPT_WASM.md`, `REFACTORING_PLAN.md`, `REFACTORING_TASKS.md` + +**Three-tier performance system**: +1. JavaScript fallback (always available) +2. WASM acceleration (2-10x faster for large operations) +3. Parallel/multicore execution (2-4x additional speedup) + +### Build System + +**Gulp-based build** (`gulpfile.js`): +- Compiles ES modules to CommonJS and ESM formats +- Creates browser bundle via Webpack +- Generates documentation from JSDoc comments +- Creates entry point files with dependency graphs + +**Outputs** (in `lib/`): +- `lib/esm/` - ES modules +- `lib/cjs/` - CommonJS (with package.json type marker) +- `lib/browser/math.js` - UMD browser bundle +- `lib/typescript/` - Compiled TypeScript (when present) +- `lib/wasm/` - WASM modules (when built) + +## Implementing a New Function + +Follow these steps when adding a new function: + +1. **Create the function file**: + - Location: `src/function//.js` (or `.ts` if TypeScript) + - Use factory function pattern with typed-function + - Document with JSDoc (used for auto-generated docs) + +2. **Register in factory index**: + - Add to `src/factoriesAny.js` + - Add to `src/factoriesNumber.js` if applicable (number-only version) + +3. **Add embedded documentation**: + - Create: `src/expression/embeddedDocs/function//.js` + - Register in: `src/expression/embeddedDocs/embeddedDocs.js` + +4. **Write tests**: + - Create: `test/unit-tests/function//.test.js` + - Use Mocha test framework + +5. **Add TypeScript definitions** (in `types/index.d.ts`): + - Add to `interface MathJsInstance` (instance method) + - Add to `interface MathJsChain` (chained method) + - Add to static exports section + - Add to dependencies section (`Dependencies`) + - Write type tests in `test/typescript-tests/testTypes.ts` + - See `types/EXPLANATION.md` for detailed guidance + +6. **Verify**: + ```bash + npm run lint # Check code style + npm run format # Auto-fix style issues + npm test # Run tests + npm run test:types # Test TypeScript definitions + ``` + +## Key Patterns and Conventions + +### Factory Function Pattern + +```javascript +// Example: createAdd factory function +export const createAdd = /* #__PURE__ */ factory( + 'add', + ['typed', 'matrix', 'equalScalar', 'addScalar'], + ({ typed, matrix, equalScalar, addScalar }) => { + return typed('add', { + 'number, number': (x, y) => x + y, + 'Complex, Complex': (x, y) => x.add(y), + 'BigNumber, BigNumber': (x, y) => x.plus(y), + 'Matrix, Matrix': (x, y) => algorithm13(x, y, addScalar), + // ... more signatures + }) + } +) +``` + +### Typed Function Pattern + +Functions support multiple type signatures via `typed-function`: +- Automatically dispatches to correct implementation based on argument types +- Supports automatic type conversions +- Can be extended with new types dynamically + +### ES Module Requirements + +- All import statements **must** include file extensions (`.js` or `.ts`) +- Configure your IDE to add extensions on auto-import +- Incorrect: `import { foo } from './bar'` +- Correct: `import { foo } from './bar.js'` + +### Code Style + +- Follows [JavaScript Standard Style](https://standardjs.com/) +- Enforced via ESLint (`.eslintrc.cjs`) +- Auto-formatted via Prettier (`.prettierrc.cjs`) +- Use `npm run format` to auto-fix + +### Test Organization + +- Test files mirror source structure: `src/function/arithmetic/add.js` → `test/unit-tests/function/arithmetic/add.test.js` +- Use descriptive test names +- Test all type signatures +- Test edge cases and error conditions + +## TypeScript Migration (for contributors) + +**Converting a file to TypeScript**: + +```bash +# Use migration tool (handles basic conversion) +node tools/migrate-to-ts.js --file src/path/to/file.js + +# Then manually: +# 1. Add proper type annotations +# 2. Update imports to reference .ts files where applicable +# 3. Add TypeScript definitions to types/index.d.ts +# 4. Test thoroughly + +npm run compile:ts # Verify compilation +npm test # Verify tests pass +npm run test:types # Verify type definitions +``` + +**Priority files for WASM conversion**: +- See `REFACTORING_TASKS.md` for complete list +- Focus on: plain number implementations, sparse algorithms, combinatorics, numeric solvers + +## Common Development Workflows + +### Running a single test file + +```bash +npx mocha test/unit-tests/function/arithmetic/add.test.js +``` + +### Building only browser bundle + +```bash +npx gulp bundle +``` + +### Generating documentation + +```bash +npm run build:docs +``` + +### Performance benchmarking + +```bash +# Run benchmarks +npm run benchmark + +# Or manually with tinybench +node test/benchmark/.js +``` + +### Testing WASM modules + +```bash +# Build WASM first +npm run build:wasm + +# Run WASM-specific tests +npm run test:wasm # (if available) + +# Or test manually in Node +node -e "import('./lib/wasm/index.js').then(wasm => console.log(wasm))" +``` + +## Important Notes + +### Compatibility + +- Requires Node.js >= 18 +- Supports ES2020+ JavaScript engines +- Browser support: Chrome, Firefox, Safari, Edge (modern versions) + +### Performance Considerations + +- For large matrix operations (>1000×1000), WASM provides 5-25x speedup +- Parallel computing requires 4+ cores for significant benefit +- The library automatically selects optimal implementation based on operation size + +### Common Gotchas + +1. **File extensions required**: Always use `.js` in imports, even in TypeScript files during transition +2. **Factory dependencies**: When adding function dependencies, ensure they're declared in factory function parameters +3. **TypeScript definitions**: Must be added in **multiple** places (see `types/EXPLANATION.md`) +4. **Don't commit generated files**: Only commit changes in `src/`, not `lib/` or `dist/` +5. **Pull requests go to `develop` branch**, not `master` + +## MCP Servers Available + +This project has access to several MCP (Model Context Protocol) servers configured in `.mcp.json`: + +### Core Development Tools + +- **sequential-thinking-mcp** - Structured reasoning and problem-solving +- **deepthinking-mcp** - 18 advanced reasoning modes with taxonomy-based classification + - Use for complex theoretical physics, mathematical proofs, algorithm design + - `mcp__deepthinking-mcp__deepthinking` tool with modes: sequential, mathematics, physics, hybrid, etc. + +### Mathematical Tools + +- **math-mcp** - WASM-accelerated mathematical operations server + - Provides matrix operations, statistics, and symbolic math tools + - Multi-tier acceleration: mathjs → WASM → WebWorkers + - Use for performance testing and validation of math.js implementations + +- **fermat-mcp** - Advanced mathematical theorem proving and symbolic computation + +### Knowledge Management + +- **memory-mcp** - Persistent knowledge graph with 45 tools + - **Critical**: Use this to maintain context across sessions + - Store project learnings, architectural decisions, refactoring progress + - See "Memory Usage Guidelines" section below + +### Search & Navigation + +- **everything-mcp** - Fast file system search (Windows Everything integration) +- **fzf-mcp** - Fuzzy file finder + +### Other Tools + +- **playwright** - Browser automation and testing +- **time** - Time and date utilities +- **substack-mcp** - Substack API integration for documentation publishing + +## Memory Usage Guidelines + +**IMPORTANT**: Use `memory-mcp` tools to maintain continuity across sessions and prevent context loss. + +### Session Start Workflow + +```bash +# 1. Search for existing context about mathjs +mcp__memory-mcp__search_nodes with query "mathjs" or "Math.js" + +# 2. Review graph statistics +mcp__memory-mcp__get_graph_stats + +# 3. Search for specific topics if working on focused areas +mcp__memory-mcp__search_nodes with query "TypeScript refactoring" +mcp__memory-mcp__search_nodes with query "WASM acceleration" +``` + +### During Development + +Store important discoveries and decisions: + +```bash +# Create entity for new architectural patterns discovered +mcp__memory-mcp__create_entities + entities: [{ + name: "Math.js Factory Pattern Extension", + entityType: "architectural-pattern", + observations: ["Details about the pattern..."], + tags: ["mathjs", "architecture", "factory-pattern"] + }] + +# Add observations to existing "Math.js" entity +mcp__memory-mcp__add_observations + entityName: "Math.js" + observations: ["Converted 5 more arithmetic functions to TypeScript", "WASM build now includes FFT operations"] + +# Create relations between concepts +mcp__memory-mcp__create_relations + relations: [{ + from: "Math.js WASM Integration", + to: "AssemblyScript Best Practices", + relationType: "implements" + }] +``` + +### Session End Workflow + +```bash +# 1. Summarize accomplishments +mcp__memory-mcp__add_observations + entityName: "Math.js TypeScript Refactoring" + observations: ["Session 2025-11-28: Converted 10 matrix operations to TS", "All unit tests passing"] + +# 2. Record next steps +mcp__memory-mcp__add_observations + entityName: "Math.js Development Tasks" + observations: ["TODO: Convert sparse matrix algorithms", "TODO: Add WASM benchmarks"] + +# 3. Set importance for critical learnings +mcp__memory-mcp__set_importance + entityName: "Math.js Build Pipeline" + importance: 9 +``` + +### Recommended Memory Entities + +Create/update these entities for mathjs project: + +1. **"Math.js"** (importance: 10, tags: mathjs, library, mathematics, active-project) + - Main project entity with high-level observations + +2. **"Math.js TypeScript Refactoring"** (importance: 9, tags: mathjs, typescript, refactoring) + - Track refactoring progress, files converted, issues encountered + +3. **"Math.js WASM Integration"** (importance: 8, tags: mathjs, wasm, performance) + - WASM-specific learnings, performance metrics, build issues + +4. **"Math.js Factory Pattern"** (importance: 7, tags: mathjs, architecture, design-pattern) + - Document how dependency injection works, examples, gotchas + +5. **"Math.js Development Environment"** (importance: 6, tags: mathjs, development, tooling) + - Build commands, test workflows, common issues and solutions + +## Advanced Development Patterns + +### Using deepthinking-mcp for Complex Problems + +When working on complex architectural decisions or mathematical implementations: + +```bash +# For theoretical physics or advanced math +mcp__deepthinking-mcp__deepthinking + mode: "mathematics" or "physics" + prompt: "Design optimal WASM memory layout for sparse matrix operations" + +# For multi-faceted problems +mcp__deepthinking-mcp__deepthinking + mode: "hybrid" + prompt: "Analyze trade-offs between WASM binary size and performance for matrix operations" + +# For step-by-step reasoning +mcp__deepthinking-mcp__deepthinking + mode: "sequential" + prompt: "Design migration strategy for converting expression parser to TypeScript" +``` + +### Using math-mcp for Validation + +Cross-validate math.js implementations against the WASM-accelerated math-mcp server: + +```bash +# Test matrix operations +mcp__math-mcp__matrix_multiply with your test matrices + +# Compare performance characteristics +# Use math-mcp results to validate your WASM implementations + +# Test statistical functions +mcp__math-mcp__statistics with sample data +``` + +## Documentation References + +- **Main README**: `README.md` - Getting started, usage examples +- **TypeScript/WASM Guide**: `README_TYPESCRIPT_WASM.md` - Complete refactoring overview +- **Refactoring Plan**: `REFACTORING_PLAN.md` - Detailed conversion strategy +- **Type Definitions**: `types/EXPLANATION.md` - TypeScript type system guide +- **Migration Guide**: `MIGRATION_GUIDE.md` - User migration guide for TS/WASM +- **Contributing**: `CONTRIBUTING.md` - Contribution guidelines +- **Online Docs**: https://mathjs.org/docs/ - Full user documentation diff --git a/PARALLEL_COMPUTING_IMPROVEMENT_PLAN.md b/PARALLEL_COMPUTING_IMPROVEMENT_PLAN.md new file mode 100644 index 0000000000..05aa29a05b --- /dev/null +++ b/PARALLEL_COMPUTING_IMPROVEMENT_PLAN.md @@ -0,0 +1,1894 @@ +# Math.js Architecture Deep Dive and Parallel Computing Integration Analysis + +## Comprehensive Guide to Web Workers, WebAssembly, and WebGPU Integration + +Math.js implements a sophisticated factory-based architecture with immutable functions and comprehensive type support, offering significant opportunities for parallel processing integration. This extensively updated report provides actionable insights for enhancing Math.js performance through parallelization using Web Workers for CPU-based threading, WebAssembly with SIMD for near-native computational speed, and WebGPU for massively parallel GPU acceleration. Based on extensive analysis of the GitHub repository and current parallel computing best practices as of late 2025, this document serves as the definitive technical reference for the Math.js parallel computing refactoring initiative. + +--- + +## Table of Contents + +1. [Core Architecture: Factory-Based Modularity](#core-architecture-reveals-factory-based-modularity) +2. [Build System and Tooling](#build-system-employs-custom-tooling-with-modular-outputs) +3. [Expression Parser Architecture](#expression-parser-architecture-enables-symbolic-computation) +4. [Performance Bottlenecks Analysis](#performance-bottlenecks-concentrate-in-matrix-operations) +5. [Web Workers: CPU-Based Parallelization](#web-workers-cpu-based-parallelization-strategy) +6. [WebAssembly: Near-Native Performance](#webassembly-near-native-performance-acceleration) +7. [WebGPU: Massively Parallel GPU Computing](#webgpu-massively-parallel-gpu-computing) +8. [Unified Parallel Computing Architecture](#unified-parallel-computing-architecture) +9. [Integration Points and Configuration](#integration-points-enable-non-breaking-parallel-enhancements) +10. [Implementation Roadmap](#implementation-roadmap-prioritizes-incremental-adoption) +11. [Memory Management Strategies](#memory-management-strategies-require-careful-consideration) +12. [Browser Compatibility Matrix](#browser-compatibility-and-feature-detection) +13. [Performance Benchmarks and Targets](#performance-benchmarks-and-optimization-targets) +14. [Distribution Strategy](#distribution-strategy-adapts-to-parallel-capabilities) +15. [Conclusion](#conclusion) + +--- + +## Core Architecture Reveals Factory-Based Modularity + +The Math.js library organizes its codebase around an **immutable factory system** that enables exceptional modularity and extensibility. The source code, located in the `src/` directory, follows a clear domain-based structure with mathematical functions organized into categories like arithmetic, algebra, matrix, and trigonometry. Each function exists as a factory that declares its dependencies and creates immutable function instances through dependency injection. This architectural pattern proves particularly advantageous for parallel computing integration, as the immutable nature of functions ensures thread safety without requiring explicit synchronization mechanisms. + +The central registry files **`src/factoriesAny.js`** and **`src/factoriesNumber.js`** serve as the heart of this system. These files export all available factory functions, with factoriesAny providing full data type support while factoriesNumber offers lightweight, number-only implementations for applications requiring minimal bundle sizes. This dual-track approach enables tree-shaking and allows developers to import only the functionality they need. For parallel computing, this modularity means that parallel implementations can be added as alternative factories that the system selects based on runtime capabilities and data size thresholds. + +The **typed-function library** integration provides the foundation for Math.js's polymorphic behavior, enabling functions to handle multiple data types seamlessly. Each mathematical operation defines type signatures that specify implementations for different input combinations—numbers, BigNumbers, Complex numbers, Matrices, and more. This system automatically handles type conversions and dispatches to the appropriate implementation at runtime, though it introduces performance overhead that becomes significant for intensive computations. The parallel computing integration must work within this type system, providing specialized implementations that bypass type-checking overhead for performance-critical paths while maintaining the ergonomic API surface. + +--- + +## Build System Employs Custom Tooling with Modular Outputs + +Math.js uses a **custom build system** rather than webpack or rollup, generating multiple output formats to support different deployment scenarios. The build process creates two primary variants: **"any"** functions supporting all data types and **"number"** functions optimized for numeric operations only. These compile to ES modules, CommonJS, and UMD bundles stored in the `lib/` directory hierarchy. The parallel computing refactoring extends this build system to incorporate WebAssembly compilation through AssemblyScript, worker bundling, and conditional inclusion of GPU compute shaders. + +The testing framework relies on **Mocha** with a comprehensive test suite mirroring the source structure. Tests are organized by mathematical category in `./test/unit-tests/function/[category]/`, with additional TypeScript definition tests and browser compatibility testing through LambdaTest. The continuous integration pipeline runs tests across multiple Node.js versions and browsers, ensuring broad compatibility. For parallel implementations, the test suite must be extended to verify numerical correctness across all parallel backends, comparing results against the reference serial implementations with appropriate floating-point tolerances. + +Package dependencies reveal strategic choices for mathematical capabilities: **decimal.js** provides arbitrary precision arithmetic, **complex.js** handles complex number operations, and **fraction.js** enables exact rational arithmetic. The library implements its own Matrix classes—**DenseMatrix** for regular matrices and **SparseMatrix** using a JavaScript port of the CSparse library for efficient sparse matrix operations. These matrix implementations serve as primary targets for parallelization, with DenseMatrix operations particularly well-suited for WebGPU acceleration due to their regular memory access patterns. + +--- + +## Expression Parser Architecture Enables Symbolic Computation + +The expression parser implements a **recursive descent design pattern** that creates Abstract Syntax Tree (AST) nodes representing mathematical expressions. Located in `src/expression/`, the parser supports full mathematical notation including operators, functions, variables, and object property access. Each AST node type (SymbolNode, OperatorNode, FunctionNode, etc.) provides methods for compilation, evaluation, string conversion, and LaTeX output. The parser architecture offers opportunities for parallel evaluation of independent subexpressions, particularly in batch evaluation scenarios. + +The **two-phase processing model**—parse then compile—optimizes performance for repeated evaluations. Parsing creates the AST structure while compilation generates executable JavaScript functions. Compiled expressions execute significantly faster than repeated parsing, though they still carry overhead from the typed-function system. For parallel computing, the compilation phase can be enhanced to detect parallelizable patterns and generate code that dispatches to appropriate parallel backends based on expression characteristics and runtime environment. + +The plugin system leverages **`math.import()`** to add custom functions to Math.js instances. The factory pattern enables clean extension points where new functions automatically inherit type support through dependency injection. Custom data types integrate seamlessly with the typed-function system, and transform functions provide a layer between the expression parser and core functions for tasks like index conversion. This extensibility mechanism allows parallel implementations to be registered as alternative function implementations without modifying core library code. + +--- + +## Performance Bottlenecks Concentrate in Matrix Operations + +GitHub issue analysis reveals critical performance problems that parallel processing directly addresses. **Matrix multiplication** emerges as the most severe bottleneck, with Issue #275 documenting cases where 1600×4 matrix operations take over 10 minutes compared to under 1 minute in numeric.js. Memory consumption scales poorly, reaching 2GB+ for large matrix operations due to the immutable design creating new matrices for each operation. These bottlenecks represent the highest-value targets for parallelization. + +The **determinant calculation** shows extreme performance gaps, running approximately **100x slower** than specialized libraries according to Issue #908 benchmarks. The typed-function overhead contributes 15-60x slowdowns compared to native JavaScript functions, as heavyweight implementations supporting multiple data types create dispatch overhead even for simple numeric operations. WebAssembly implementations can bypass this overhead entirely for numeric operations while WebGPU can achieve even greater speedups for sufficiently large matrices. + +Expression evaluation performance suffers from similar issues, with **`math.eval('1')`** taking 80+ milliseconds due to parsing and compilation overhead. The lack of in-place operations for large matrices creates garbage collection pressure through frequent object allocation. Browser performance varies significantly, with Chrome showing 10x better performance than Firefox for certain operations. The parallel computing integration addresses these issues through multiple complementary strategies: Web Workers prevent main thread blocking, WebAssembly provides fast numeric computation, and WebGPU enables massive parallelism for large-scale operations. + +### Quantified Performance Gaps + +| Operation | Current Math.js | Optimized Alternative | Gap Factor | +|-----------|----------------|----------------------|------------| +| Matrix Multiply (1000×1000) | ~45 seconds | ~0.5 seconds (native) | 90x | +| Determinant (500×500) | ~120 seconds | ~1.2 seconds (LAPACK) | 100x | +| SVD Decomposition (500×500) | ~300 seconds | ~3 seconds (native) | 100x | +| Element-wise Operations (10M) | ~800ms | ~50ms (SIMD) | 16x | + +--- + +## Web Workers: CPU-Based Parallelization Strategy + +### Architecture Overview + +Web Workers provide the foundation for CPU-based parallelization in Math.js, enabling background thread execution without blocking the main thread. The implementation follows a **worker pool pattern** that maintains a configurable number of workers sized to `navigator.hardwareConcurrency`, distributing tasks efficiently while avoiding oversubscription. + +### Worker Pool Implementation Design + +```javascript +// Core Worker Pool Architecture for Math.js +class MathWorkerPool { + constructor(config = {}) { + this.config = { + enabled: config.enabled !== false, + maxWorkers: config.maxWorkers || navigator.hardwareConcurrency || 4, + minTaskSize: config.minTaskSize || { matrix: 100, array: 1000 }, + timeout: config.timeout || 30000, + useSharedMemory: config.useSharedMemory !== false + }; + + this.workers = []; + this.taskQueue = []; + this.activeJobs = new Map(); + this.capabilities = null; + } + + async initialize() { + // Detect environment capabilities + this.capabilities = await this.detectCapabilities(); + + // Create worker pool based on available concurrency + for (let i = 0; i < this.config.maxWorkers; i++) { + const worker = await this.createMathWorker(i); + this.workers.push(worker); + } + + return this; + } + + async detectCapabilities() { + return { + sharedArrayBuffer: typeof SharedArrayBuffer !== 'undefined', + atomics: typeof Atomics !== 'undefined', + transferable: true, + crossOriginIsolated: typeof crossOriginIsolated !== 'undefined' + && crossOriginIsolated + }; + } + + shouldUseParallel(operation, data) { + if (!this.config.enabled) return false; + + const size = this.getDataSize(data); + const threshold = this.config.minTaskSize[this.getDataType(data)] || 1000; + const complexity = this.getOperationComplexity(operation); + + // Account for operation complexity in threshold calculation + return size >= (threshold / complexity); + } +} +``` + +### SharedArrayBuffer for Zero-Copy Data Transfer + +The most significant performance optimization for Web Workers involves **SharedArrayBuffer** for true shared memory access between threads. Unlike traditional `postMessage` which copies data, SharedArrayBuffer enables zero-copy data sharing that completes in under 1ms for 100MB data compared to 100-300ms for structured cloning. + +```javascript +// SharedArrayBuffer-based Matrix Transfer +class SharedMatrixBuffer { + constructor(rows, cols) { + this.rows = rows; + this.cols = cols; + this.bytesPerElement = Float64Array.BYTES_PER_ELEMENT; + + // Allocate shared buffer for matrix data plus metadata + const dataSize = rows * cols * this.bytesPerElement; + const metadataSize = 3 * Int32Array.BYTES_PER_ELEMENT; // rows, cols, status + + this.buffer = new SharedArrayBuffer(dataSize + metadataSize); + this.metadata = new Int32Array(this.buffer, 0, 3); + this.data = new Float64Array(this.buffer, metadataSize, rows * cols); + + // Store dimensions in metadata + Atomics.store(this.metadata, 0, rows); + Atomics.store(this.metadata, 1, cols); + Atomics.store(this.metadata, 2, 0); // Status: 0 = ready + } + + // Workers can directly read/write this.data without copying + // Synchronization uses Atomics for the status field + markProcessing() { + Atomics.store(this.metadata, 2, 1); + } + + markComplete() { + Atomics.store(this.metadata, 2, 2); + Atomics.notify(this.metadata, 2); + } + + waitForComplete() { + while (Atomics.load(this.metadata, 2) !== 2) { + Atomics.wait(this.metadata, 2, 1); + } + } +} +``` + +### Security Requirements: Cross-Origin Isolation + +SharedArrayBuffer requires specific HTTP headers for security due to Spectre vulnerabilities. The Math.js documentation should clearly communicate these requirements: + +``` +Cross-Origin-Embedder-Policy: require-corp +Cross-Origin-Opener-Policy: same-origin +``` + +When these headers are not present, the parallel computing module automatically falls back to **Transferable Objects** for efficient data movement or structured cloning for smaller datasets. + +### Parallel Matrix Operations via Web Workers + +```javascript +// Parallel Matrix Multiplication using Block Decomposition +async function parallelMatrixMultiply(A, B, workerPool) { + const [m, k] = [A.rows, A.cols]; + const [_, n] = [B.rows, B.cols]; + + // Determine optimal block size based on worker count and cache efficiency + const numWorkers = workerPool.workers.length; + const blockSize = Math.ceil(m / numWorkers); + + // Create shared buffers for input matrices + const sharedA = new SharedMatrixBuffer(m, k); + const sharedB = new SharedMatrixBuffer(k, n); + const sharedC = new SharedMatrixBuffer(m, n); + + // Copy input data to shared buffers (one-time cost) + sharedA.data.set(A.flatten()); + sharedB.data.set(B.flatten()); + + // Dispatch row blocks to workers + const tasks = []; + for (let i = 0; i < numWorkers; i++) { + const startRow = i * blockSize; + const endRow = Math.min(startRow + blockSize, m); + + if (startRow < m) { + tasks.push(workerPool.execute('multiplyBlock', { + sharedA: sharedA.buffer, + sharedB: sharedB.buffer, + sharedC: sharedC.buffer, + startRow, endRow, m, k, n + })); + } + } + + // Wait for all workers to complete + await Promise.all(tasks); + + // Result is already in sharedC due to shared memory + return new DenseMatrix(sharedC.data, [m, n]); +} +``` + +### Worker Script Implementation + +```javascript +// math-worker.js - Worker thread implementation +import { create, all } from 'mathjs'; +const math = create(all); + +self.addEventListener('message', async (event) => { + const { type, taskId, operation, data } = event.data; + + switch (operation) { + case 'multiplyBlock': + executeMatrixMultiplyBlock(taskId, data); + break; + case 'statisticalChunk': + executeStatisticalChunk(taskId, data); + break; + case 'eigenvalueIteration': + executeEigenvalueIteration(taskId, data); + break; + } +}); + +function executeMatrixMultiplyBlock(taskId, params) { + const { sharedA, sharedB, sharedC, startRow, endRow, m, k, n } = params; + + // Create views on shared memory + const A = new Float64Array(sharedA); + const B = new Float64Array(sharedB); + const C = new Float64Array(sharedC); + + // Compute assigned block with cache-friendly access pattern + for (let i = startRow; i < endRow; i++) { + for (let j = 0; j < n; j++) { + let sum = 0; + for (let p = 0; p < k; p++) { + sum += A[i * k + p] * B[p * n + j]; + } + // Atomic write not needed as each worker writes to disjoint rows + C[i * n + j] = sum; + } + } + + self.postMessage({ type: 'complete', taskId }); +} + +function executeStatisticalChunk(taskId, params) { + const { sharedData, startIdx, endIdx, operation } = params; + const data = new Float64Array(sharedData, startIdx * 8, endIdx - startIdx); + + let result; + switch (operation) { + case 'sum': + result = { sum: kahanSum(data), count: data.length }; + break; + case 'variance': + result = welfordVariance(data); + break; + case 'median': + result = { sorted: quickSelect(data, Math.floor(data.length / 2)) }; + break; + } + + self.postMessage({ type: 'result', taskId, result }); +} + +// Numerically stable summation using Kahan algorithm +function kahanSum(data) { + let sum = 0, c = 0; + for (let i = 0; i < data.length; i++) { + const y = data[i] - c; + const t = sum + y; + c = (t - sum) - y; + sum = t; + } + return sum; +} +``` + +### Expected Performance Improvements from Web Workers + +| Operation | Data Size | Serial Time | Parallel Time (4 workers) | Speedup | +|-----------|-----------|-------------|---------------------------|---------| +| Matrix Multiply | 1000×1000 | 2500ms | 700ms | 3.6x | +| Statistical Mean | 10M elements | 45ms | 15ms | 3.0x | +| Element-wise Ops | 10M elements | 120ms | 35ms | 3.4x | +| SVD (Power Method) | 500×500 | 800ms | 250ms | 3.2x | + +--- + +## WebAssembly: Near-Native Performance Acceleration + +### Overview and Benefits + +WebAssembly provides near-native execution speed for computationally intensive operations, achieving **1.7-4.5x speedups** through SIMD vectorization and optimized numeric processing. Unlike Web Workers which provide parallelism through threading, WebAssembly accelerates individual computations through efficient bytecode execution and access to CPU SIMD instructions. + +### AssemblyScript Integration Strategy + +Math.js adopts **AssemblyScript** as the WebAssembly source language due to its TypeScript-like syntax and excellent binary size characteristics. AssemblyScript generates remarkably small binaries—as little as 959 bytes gzipped for the stub runtime—while providing direct access to WebAssembly features including SIMD instructions. + +### SIMD-Accelerated Matrix Operations + +WebAssembly SIMD uses 128-bit registers that can process: +- 4 × 32-bit floats simultaneously +- 2 × 64-bit floats simultaneously +- 16 × 8-bit integers simultaneously +- 8 × 16-bit integers simultaneously + +```typescript +// AssemblyScript SIMD Matrix Multiplication Kernel +// File: src/core/wasm/kernels/matrix.ts + +// Cache-friendly tiled matrix multiplication with SIMD +export function matrixMultiplySIMD( + aPtr: usize, + bPtr: usize, + cPtr: usize, + M: i32, + K: i32, + N: i32 +): void { + const TILE_SIZE: i32 = 64; // Optimized for L1 cache + + // Clear result matrix + memory.fill(cPtr, 0, M * N * sizeof()); + + // Tiled multiplication with SIMD inner loop + for (let ii: i32 = 0; ii < M; ii += TILE_SIZE) { + const iMax = min(ii + TILE_SIZE, M); + + for (let kk: i32 = 0; kk < K; kk += TILE_SIZE) { + const kMax = min(kk + TILE_SIZE, K); + + for (let jj: i32 = 0; jj < N; jj += TILE_SIZE) { + const jMax = min(jj + TILE_SIZE, N); + + // Process tile with SIMD + for (let i = ii; i < iMax; i++) { + for (let k = kk; k < kMax; k++) { + // Broadcast A[i,k] to all SIMD lanes + const aik = v128.splat( + load(aPtr + (i * K + k) * sizeof()) + ); + + // Process 2 elements at a time (128-bit = 2 × f64) + let j = jj; + for (; j + 1 < jMax; j += 2) { + const bOffset = (k * N + j) * sizeof(); + const cOffset = (i * N + j) * sizeof(); + + // Load B[k,j:j+2] + const bkj = v128.load(bPtr + bOffset); + + // Load current C[i,j:j+2] + const cij = v128.load(cPtr + cOffset); + + // C[i,j:j+2] += A[i,k] * B[k,j:j+2] + const product = f64x2.mul(aik, bkj); + const result = f64x2.add(cij, product); + + // Store result + v128.store(cPtr + cOffset, result); + } + + // Handle odd column + if (j < jMax) { + const bVal = load(bPtr + (k * N + j) * sizeof()); + const cVal = load(cPtr + (i * N + j) * sizeof()); + const aVal = load(aPtr + (i * K + k) * sizeof()); + store(cPtr + (i * N + j) * sizeof(), cVal + aVal * bVal); + } + } + } + } + } + } +} + +// Strassen's Algorithm for Large Matrices +export function strassenMultiply( + aPtr: usize, + bPtr: usize, + cPtr: usize, + n: i32 +): void { + const STRASSEN_THRESHOLD: i32 = 128; + + if (n <= STRASSEN_THRESHOLD) { + matrixMultiplySIMD(aPtr, bPtr, cPtr, n, n, n); + return; + } + + // Recursive Strassen decomposition for O(n^2.807) complexity + const halfN = n >> 1; + const quadSize = halfN * halfN * sizeof(); + + // Allocate temporary matrices for Strassen products + const m1 = heap.alloc(quadSize); + const m2 = heap.alloc(quadSize); + // ... (full Strassen implementation) + + heap.free(m1); + heap.free(m2); +} +``` + +### Statistical Operations with SIMD + +```typescript +// Numerically stable parallel sum using Kahan summation with SIMD +export function parallelSumSIMD(dataPtr: usize, length: i32): f64 { + // Initialize SIMD accumulators + let sum_vec = f64x2.splat(0.0); + let comp_vec = f64x2.splat(0.0); // Kahan compensation + + let i: i32 = 0; + + // SIMD loop processing 2 elements at a time + for (; i + 1 < length; i += 2) { + const y_vec = f64x2.sub( + v128.load(dataPtr + i * sizeof()), + comp_vec + ); + const t_vec = f64x2.add(sum_vec, y_vec); + comp_vec = f64x2.sub(f64x2.sub(t_vec, sum_vec), y_vec); + sum_vec = t_vec; + } + + // Horizontal reduction of SIMD vector + let sum = f64x2.extract_lane(sum_vec, 0) + f64x2.extract_lane(sum_vec, 1); + let comp = f64x2.extract_lane(comp_vec, 0) + f64x2.extract_lane(comp_vec, 1); + + // Handle remaining element + if (i < length) { + const y = load(dataPtr + i * sizeof()) - comp; + const t = sum + y; + comp = (t - sum) - y; + sum = t; + } + + return sum; +} + +// Welford's online variance algorithm +export function welfordVarianceSIMD(dataPtr: usize, length: i32): f64 { + let count: f64 = 0; + let mean: f64 = 0; + let m2: f64 = 0; + + for (let i: i32 = 0; i < length; i++) { + count += 1; + const x = load(dataPtr + i * sizeof()); + const delta = x - mean; + mean += delta / count; + const delta2 = x - mean; + m2 += delta * delta2; + } + + return m2 / (count - 1); // Sample variance +} +``` + +### WebAssembly Loader and Integration + +```javascript +// WasmLoader.js - Runtime WebAssembly module management +export class WasmLoader { + constructor() { + this.module = null; + this.instance = null; + this.memory = null; + this.initialized = false; + this.capabilities = this.detectCapabilities(); + } + + detectCapabilities() { + const capabilities = { + webassembly: typeof WebAssembly !== 'undefined', + simd: false, + threads: false, + relaxedSimd: false + }; + + if (capabilities.webassembly) { + // Test SIMD support + try { + const simdTest = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7b, 0x03, + 0x02, 0x01, 0x00, 0x0a, 0x0a, 0x01, 0x08, 0x00, + 0x41, 0x00, 0xfd, 0x0f, 0x0b + ]); + new WebAssembly.Module(simdTest); + capabilities.simd = true; + } catch (e) {} + + // Test threading support + capabilities.threads = typeof SharedArrayBuffer !== 'undefined'; + } + + return capabilities; + } + + async initialize(wasmPath = '/dist/wasm/math-kernels.wasm') { + if (!this.capabilities.webassembly) { + console.warn('WebAssembly not supported'); + return false; + } + + try { + // Select appropriate WASM variant based on capabilities + let actualPath = wasmPath; + if (this.capabilities.simd) { + actualPath = wasmPath.replace('.wasm', '.simd.wasm'); + } + + const response = await fetch(actualPath); + const bytes = await response.arrayBuffer(); + + // Create shared memory for efficient data transfer + this.memory = new WebAssembly.Memory({ + initial: 256, // 16MB initial + maximum: 2048, // 128MB maximum + shared: this.capabilities.threads + }); + + // Compile and instantiate + this.module = await WebAssembly.compile(bytes); + this.instance = await WebAssembly.instantiate(this.module, { + env: { + memory: this.memory, + abort: (msg, file, line) => { + console.error(`WASM abort at ${file}:${line}: ${msg}`); + } + }, + Math: Math + }); + + this.initialized = true; + return true; + } catch (error) { + console.error('WebAssembly initialization failed:', error); + return false; + } + } + + // High-level API for matrix operations + matrixMultiply(A, B) { + if (!this.initialized) throw new Error('WASM not initialized'); + + const [m, k1] = A.size(); + const [k2, n] = B.size(); + if (k1 !== k2) throw new Error('Dimension mismatch'); + + // Allocate WASM memory + const aPtr = this.allocate(m * k1 * 8); + const bPtr = this.allocate(k1 * n * 8); + const cPtr = this.allocate(m * n * 8); + + // Copy input data + const aView = new Float64Array(this.memory.buffer, aPtr, m * k1); + const bView = new Float64Array(this.memory.buffer, bPtr, k1 * n); + aView.set(A.valueOf().flat()); + bView.set(B.valueOf().flat()); + + // Execute WASM kernel + if (this.capabilities.simd) { + this.instance.exports.matrixMultiplySIMD(aPtr, bPtr, cPtr, m, k1, n); + } else { + this.instance.exports.matrixMultiply(aPtr, bPtr, cPtr, m, k1, n); + } + + // Read result + const cView = new Float64Array(this.memory.buffer, cPtr, m * n); + const result = Array.from(cView); + + // Free WASM memory + this.deallocate(aPtr); + this.deallocate(bPtr); + this.deallocate(cPtr); + + // Reshape to 2D matrix + const matrix = []; + for (let i = 0; i < m; i++) { + matrix.push(result.slice(i * n, (i + 1) * n)); + } + return matrix; + } + + allocate(bytes) { + return this.instance.exports.allocate(bytes); + } + + deallocate(ptr) { + this.instance.exports.deallocate(ptr); + } +} +``` + +### AssemblyScript Build Configuration + +```json +// asconfig.json - AssemblyScript compiler configuration +{ + "extends": "./node_modules/assemblyscript/std/assembly.json", + "entries": ["./src/core/wasm/kernels/index.ts"], + "options": { + "runtime": "stub", + "exportRuntime": false, + "initialMemory": 256, + "maximumMemory": 2048, + "importMemory": true, + "exportTable": true + }, + "targets": { + "release": { + "outFile": "./dist/wasm/math-kernels.wasm", + "optimizeLevel": 3, + "shrinkLevel": 2, + "converge": true, + "noAssert": true + }, + "simd": { + "outFile": "./dist/wasm/math-kernels.simd.wasm", + "optimizeLevel": 3, + "shrinkLevel": 2, + "converge": true, + "noAssert": true, + "enable": ["simd", "relaxed-simd", "bulk-memory"] + }, + "debug": { + "outFile": "./dist/wasm/math-kernels.debug.wasm", + "debug": true, + "sourceMap": true, + "optimizeLevel": 0 + } + } +} +``` + +### WebAssembly Performance Benchmarks + +| Operation | Pure JS | WASM | WASM+SIMD | Improvement | +|-----------|---------|------|-----------|-------------| +| Matrix Multiply 64×64 | 15ms | 8ms | 4ms | 3.75x | +| Matrix Multiply 256×256 | 850ms | 280ms | 95ms | 8.9x | +| Matrix Multiply 1024×1024 | 45000ms | 8500ms | 2800ms | 16x | +| Vector Dot Product (1M) | 12ms | 4ms | 1.8ms | 6.7x | +| Array Sum (10M) | 45ms | 15ms | 6ms | 7.5x | +| Variance Calculation (1M) | 38ms | 12ms | 5ms | 7.6x | + +--- + +## WebGPU: Massively Parallel GPU Computing + +### Overview and Browser Support Status + +WebGPU represents the most significant advancement in web-based parallel computing, providing direct access to modern GPU capabilities through compute shaders. As of late 2025, WebGPU has achieved **cross-browser support**: Chrome/Edge since version 113 (April 2023), Firefox since version 141 (July 2025), and Safari since Safari 26 (June 2025). This milestone means approximately **85% of desktop browsers** now support WebGPU, making it viable for production deployment with appropriate fallbacks. + +WebGPU's compute shaders enable **massive parallelism** that can outperform both CPU-based Web Workers and WebAssembly for sufficiently large operations. Benchmarks show matrix multiplication achieving **376 GFLOPS** on Windows with matrix size 4096—2.4x faster than WebGL-based approaches. For operations where the overhead of GPU data transfer is amortized across substantial computation, WebGPU provides unmatched performance. + +### WebGPU Architecture for Math.js + +```javascript +// WebGPU Compute Engine for Math.js +export class WebGPUCompute { + constructor() { + this.device = null; + this.adapter = null; + this.initialized = false; + this.shaderModules = new Map(); + this.pipelineCache = new Map(); + } + + async initialize() { + if (!navigator.gpu) { + console.warn('WebGPU not supported'); + return false; + } + + try { + // Request adapter with high-performance preference + this.adapter = await navigator.gpu.requestAdapter({ + powerPreference: 'high-performance' + }); + + if (!this.adapter) { + console.warn('No WebGPU adapter found'); + return false; + } + + // Request device with required features + this.device = await this.adapter.requestDevice({ + requiredFeatures: [], + requiredLimits: { + maxStorageBufferBindingSize: + this.adapter.limits.maxStorageBufferBindingSize, + maxComputeWorkgroupsPerDimension: 65535 + } + }); + + // Pre-compile common shader modules + await this.compileShaders(); + + this.initialized = true; + return true; + } catch (error) { + console.error('WebGPU initialization failed:', error); + return false; + } + } + + async compileShaders() { + // Matrix multiplication shader + this.shaderModules.set('matmul', this.device.createShaderModule({ + code: this.getMatrixMultiplyShader() + })); + + // Element-wise operations shader + this.shaderModules.set('elementwise', this.device.createShaderModule({ + code: this.getElementWiseShader() + })); + + // Statistical operations shader + this.shaderModules.set('statistics', this.device.createShaderModule({ + code: this.getStatisticsShader() + })); + } + + getMatrixMultiplyShader() { + return /* wgsl */` + struct Matrix { + rows: u32, + cols: u32, + data: array, + } + + @group(0) @binding(0) var matrixA: Matrix; + @group(0) @binding(1) var matrixB: Matrix; + @group(0) @binding(2) var matrixC: Matrix; + + // Tile size for shared memory optimization + const TILE_SIZE: u32 = 16u; + + var tileA: array, 16>; + var tileB: array, 16>; + + @compute @workgroup_size(16, 16) + fn main( + @builtin(global_invocation_id) global_id: vec3, + @builtin(local_invocation_id) local_id: vec3, + @builtin(workgroup_id) workgroup_id: vec3 + ) { + let row = global_id.y; + let col = global_id.x; + let M = matrixA.rows; + let K = matrixA.cols; + let N = matrixB.cols; + + // Guard against out-of-bounds work group sizes + if (row >= M || col >= N) { + return; + } + + var sum: f32 = 0.0; + let numTiles = (K + TILE_SIZE - 1u) / TILE_SIZE; + + // Tiled matrix multiplication using shared memory + for (var t: u32 = 0u; t < numTiles; t = t + 1u) { + // Collaborative loading of tiles into shared memory + let tileCol = t * TILE_SIZE + local_id.x; + let tileRow = t * TILE_SIZE + local_id.y; + + if (row < M && tileCol < K) { + tileA[local_id.y][local_id.x] = matrixA.data[row * K + tileCol]; + } else { + tileA[local_id.y][local_id.x] = 0.0; + } + + if (tileRow < K && col < N) { + tileB[local_id.y][local_id.x] = matrixB.data[tileRow * N + col]; + } else { + tileB[local_id.y][local_id.x] = 0.0; + } + + // Synchronize to ensure tile is fully loaded + workgroupBarrier(); + + // Compute partial dot product for this tile + for (var k: u32 = 0u; k < TILE_SIZE; k = k + 1u) { + sum = sum + tileA[local_id.y][k] * tileB[k][local_id.x]; + } + + // Synchronize before loading next tile + workgroupBarrier(); + } + + // Store result + matrixC.data[row * N + col] = sum; + } + `; + } + + getElementWiseShader() { + return /* wgsl */` + @group(0) @binding(0) var inputA: array; + @group(0) @binding(1) var inputB: array; + @group(0) @binding(2) var output: array; + @group(0) @binding(3) var params: vec4; // length, operation, 0, 0 + + // Operations: 0=add, 1=subtract, 2=multiply, 3=divide, 4=pow + + @compute @workgroup_size(256) + fn main(@builtin(global_invocation_id) global_id: vec3) { + let idx = global_id.x; + let length = params.x; + let operation = params.y; + + if (idx >= length) { + return; + } + + let a = inputA[idx]; + let b = inputB[idx]; + + switch operation { + case 0u: { output[idx] = a + b; } + case 1u: { output[idx] = a - b; } + case 2u: { output[idx] = a * b; } + case 3u: { output[idx] = a / b; } + case 4u: { output[idx] = pow(a, b); } + default: { output[idx] = 0.0; } + } + } + `; + } + + getStatisticsShader() { + return /* wgsl */` + @group(0) @binding(0) var input: array; + @group(0) @binding(1) var output: array; + @group(0) @binding(2) var params: vec4; // length, operation, 0, 0 + + var sharedData: array; + + // Parallel reduction for sum + @compute @workgroup_size(256) + fn reduceSum( + @builtin(global_invocation_id) global_id: vec3, + @builtin(local_invocation_id) local_id: vec3, + @builtin(workgroup_id) workgroup_id: vec3 + ) { + let idx = global_id.x; + let localIdx = local_id.x; + let length = params.x; + + // Load data into shared memory + if (idx < length) { + sharedData[localIdx] = input[idx]; + } else { + sharedData[localIdx] = 0.0; + } + + workgroupBarrier(); + + // Parallel reduction tree + for (var stride: u32 = 128u; stride > 0u; stride = stride >> 1u) { + if (localIdx < stride) { + sharedData[localIdx] = sharedData[localIdx] + sharedData[localIdx + stride]; + } + workgroupBarrier(); + } + + // First thread writes workgroup result + if (localIdx == 0u) { + output[workgroup_id.x] = sharedData[0]; + } + } + `; + } + + // High-level matrix multiplication API + async matrixMultiply(A, B) { + if (!this.initialized) throw new Error('WebGPU not initialized'); + + const M = A.length; + const K = A[0].length; + const N = B[0].length; + + // Flatten matrices + const flatA = new Float32Array(A.flat()); + const flatB = new Float32Array(B.flat()); + + // Create GPU buffers + const bufferA = this.createBuffer(flatA, GPUBufferUsage.STORAGE); + const bufferB = this.createBuffer(flatB, GPUBufferUsage.STORAGE); + const bufferC = this.device.createBuffer({ + size: M * N * 4 + 8, // +8 for rows/cols header + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC + }); + + // Create bind group + const bindGroupLayout = this.device.createBindGroupLayout({ + entries: [ + { binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } }, + { binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } }, + { binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } } + ] + }); + + const bindGroup = this.device.createBindGroup({ + layout: bindGroupLayout, + entries: [ + { binding: 0, resource: { buffer: bufferA } }, + { binding: 1, resource: { buffer: bufferB } }, + { binding: 2, resource: { buffer: bufferC } } + ] + }); + + // Create compute pipeline + const pipeline = this.device.createComputePipeline({ + layout: this.device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }), + compute: { + module: this.shaderModules.get('matmul'), + entryPoint: 'main' + } + }); + + // Dispatch compute + const commandEncoder = this.device.createCommandEncoder(); + const computePass = commandEncoder.beginComputePass(); + computePass.setPipeline(pipeline); + computePass.setBindGroup(0, bindGroup); + + // Dispatch enough workgroups to cover entire output matrix + const workgroupsX = Math.ceil(N / 16); + const workgroupsY = Math.ceil(M / 16); + computePass.dispatchWorkgroups(workgroupsX, workgroupsY); + computePass.end(); + + // Copy result to readable buffer + const readBuffer = this.device.createBuffer({ + size: M * N * 4, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ + }); + commandEncoder.copyBufferToBuffer(bufferC, 8, readBuffer, 0, M * N * 4); + + // Submit and wait + this.device.queue.submit([commandEncoder.finish()]); + await readBuffer.mapAsync(GPUMapMode.READ); + + // Read result + const resultArray = new Float32Array(readBuffer.getMappedRange().slice(0)); + readBuffer.unmap(); + + // Cleanup + bufferA.destroy(); + bufferB.destroy(); + bufferC.destroy(); + readBuffer.destroy(); + + // Reshape to 2D array + const result = []; + for (let i = 0; i < M; i++) { + result.push(Array.from(resultArray.slice(i * N, (i + 1) * N))); + } + return result; + } + + createBuffer(data, usage) { + const buffer = this.device.createBuffer({ + size: data.byteLength + 8, + usage: usage | GPUBufferUsage.COPY_DST, + mappedAtCreation: true + }); + + const mapping = new Float32Array(buffer.getMappedRange()); + mapping.set(data, 2); // Offset for header + buffer.unmap(); + + return buffer; + } +} +``` + +### WebGPU Operation Selection Criteria + +WebGPU should be used when the computational benefit outweighs the data transfer overhead: + +```javascript +// Decision logic for WebGPU usage +class ParallelComputeScheduler { + shouldUseWebGPU(operation, dataSize) { + // Size thresholds based on benchmarks + const thresholds = { + matrixMultiply: { + minDimension: 256, // GPU efficient above 256×256 + optimalDimension: 1024 + }, + elementWise: { + minElements: 100000, // 100K elements minimum + optimalElements: 1000000 + }, + reduction: { + minElements: 50000, + optimalElements: 500000 + } + }; + + const config = thresholds[operation]; + if (!config) return false; + + // Check if WebGPU is available and beneficial + if (!this.webgpu?.initialized) return false; + + // Matrix operations + if (operation === 'matrixMultiply') { + const [m, n] = dataSize; + return Math.min(m, n) >= config.minDimension; + } + + // Array operations + return dataSize >= config.minElements; + } + + // Unified dispatch that selects optimal backend + async dispatch(operation, data, options = {}) { + const size = this.getDataSize(data); + + // Priority: WebGPU > WASM+SIMD > Web Workers > Serial + if (this.shouldUseWebGPU(operation, size)) { + return this.webgpu.execute(operation, data, options); + } + + if (this.shouldUseWasm(operation, size)) { + return this.wasm.execute(operation, data, options); + } + + if (this.shouldUseWorkers(operation, size)) { + return this.workerPool.execute(operation, data, options); + } + + // Fallback to serial JavaScript + return this.executeSerial(operation, data, options); + } +} +``` + +### WebGPU Performance Characteristics + +| Matrix Size | JavaScript | WASM+SIMD | WebGPU | WebGPU Speedup | +|-------------|------------|-----------|--------|----------------| +| 128×128 | 45ms | 8ms | 12ms* | -0.7x | +| 256×256 | 350ms | 45ms | 18ms | 19x | +| 512×512 | 2800ms | 280ms | 35ms | 80x | +| 1024×1024 | 22000ms | 1800ms | 95ms | 232x | +| 2048×2048 | 180000ms | 14000ms | 380ms | 474x | +| 4096×4096 | N/A | 110000ms | 1500ms | 73x | + +*Note: WebGPU has initialization overhead that makes it slower for small matrices. The crossover point is around 200×200 matrices. + +--- + +## Unified Parallel Computing Architecture + +### Backend Selection Strategy + +The Math.js parallel computing integration implements a **unified dispatcher** that automatically selects the optimal execution backend based on operation type, data size, and available hardware capabilities: + +```javascript +// Unified Parallel Computing Dispatcher +export class ParallelDispatcher { + constructor(config = {}) { + this.config = { + autoSelect: config.autoSelect !== false, + preferGPU: config.preferGPU !== false, + fallbackToSerial: config.fallbackToSerial !== false, + ...config + }; + + this.backends = { + webgpu: null, + wasm: null, + workers: null + }; + + this.initialized = false; + } + + async initialize() { + // Initialize backends in parallel + const initPromises = []; + + // WebGPU initialization + if (typeof navigator !== 'undefined' && navigator.gpu) { + this.backends.webgpu = new WebGPUCompute(); + initPromises.push( + this.backends.webgpu.initialize() + .catch(e => { this.backends.webgpu = null; }) + ); + } + + // WebAssembly initialization + if (typeof WebAssembly !== 'undefined') { + this.backends.wasm = new WasmLoader(); + initPromises.push( + this.backends.wasm.initialize() + .catch(e => { this.backends.wasm = null; }) + ); + } + + // Web Workers initialization + if (typeof Worker !== 'undefined') { + this.backends.workers = new MathWorkerPool(this.config.workers); + initPromises.push( + this.backends.workers.initialize() + .catch(e => { this.backends.workers = null; }) + ); + } + + await Promise.all(initPromises); + this.initialized = true; + + return this.getCapabilities(); + } + + getCapabilities() { + return { + webgpu: this.backends.webgpu?.initialized ?? false, + wasm: this.backends.wasm?.initialized ?? false, + wasmSimd: this.backends.wasm?.capabilities?.simd ?? false, + workers: this.backends.workers?.workers?.length ?? 0, + sharedMemory: typeof SharedArrayBuffer !== 'undefined' + }; + } + + // Backend selection decision tree + selectBackend(operation, data) { + const size = this.getDataSize(operation, data); + const caps = this.getCapabilities(); + + // Operation-specific thresholds + const thresholds = this.getThresholds(operation); + + // WebGPU: Best for very large operations + if (caps.webgpu && size >= thresholds.webgpu) { + return 'webgpu'; + } + + // WASM+SIMD: Best for medium-large numeric operations + if (caps.wasmSimd && size >= thresholds.wasmSimd) { + return 'wasm-simd'; + } + + // WASM: Good for medium numeric operations + if (caps.wasm && size >= thresholds.wasm) { + return 'wasm'; + } + + // Workers: Good for parallel decomposable operations + if (caps.workers >= 2 && size >= thresholds.workers) { + return 'workers'; + } + + // Serial: Default fallback + return 'serial'; + } + + getThresholds(operation) { + // Empirically determined thresholds + const operationThresholds = { + 'matrix.multiply': { + webgpu: 65536, // 256×256 + wasmSimd: 10000, // 100×100 + wasm: 2500, // 50×50 + workers: 10000 // 100×100 + }, + 'matrix.det': { + webgpu: 160000, // 400×400 + wasmSimd: 22500, // 150×150 + wasm: 10000, // 100×100 + workers: 40000 // 200×200 + }, + 'matrix.eigs': { + webgpu: 90000, // 300×300 + wasmSimd: 10000, // 100×100 + wasm: 4900, // 70×70 + workers: 22500 // 150×150 + }, + 'statistics.mean': { + webgpu: 1000000, // 1M elements + wasmSimd: 50000, // 50K elements + wasm: 10000, // 10K elements + workers: 100000 // 100K elements + }, + 'default': { + webgpu: 500000, + wasmSimd: 50000, + wasm: 10000, + workers: 100000 + } + }; + + return operationThresholds[operation] || operationThresholds['default']; + } + + async execute(operation, data, options = {}) { + const backend = options.forceBackend || this.selectBackend(operation, data); + + try { + switch (backend) { + case 'webgpu': + return await this.backends.webgpu.execute(operation, data, options); + case 'wasm-simd': + case 'wasm': + return await this.backends.wasm.execute(operation, data, options); + case 'workers': + return await this.backends.workers.execute(operation, data, options); + default: + return this.executeSerial(operation, data, options); + } + } catch (error) { + if (this.config.fallbackToSerial) { + console.warn(`${backend} execution failed, falling back to serial:`, error); + return this.executeSerial(operation, data, options); + } + throw error; + } + } +} +``` + +### Capability-Based Progressive Enhancement + +```javascript +// Progressive enhancement based on detected capabilities +const parallelConfig = { + // Automatic capability detection and configuration + auto: async () => { + const dispatcher = new ParallelDispatcher(); + const caps = await dispatcher.initialize(); + + return { + enabled: caps.webgpu || caps.wasmSimd || caps.workers > 1, + primary: caps.webgpu ? 'webgpu' : + caps.wasmSimd ? 'wasm-simd' : + caps.workers > 1 ? 'workers' : 'serial', + fallbackChain: ['webgpu', 'wasm-simd', 'wasm', 'workers', 'serial'] + .filter(b => { + switch(b) { + case 'webgpu': return caps.webgpu; + case 'wasm-simd': return caps.wasmSimd; + case 'wasm': return caps.wasm; + case 'workers': return caps.workers > 0; + default: return true; + } + }), + capabilities: caps + }; + } +}; +``` + +--- + +## Integration Points Enable Non-Breaking Parallel Enhancements + +### Configuration System Integration + +The factory system provides natural integration points for parallel processing without breaking changes. The configuration system, accessible through **`math.config()`**, accommodates comprehensive parallel processing configuration: + +```javascript +// Enhanced Math.js configuration with parallel computing options +math.config({ + // Existing configuration preserved + number: 'number', + precision: 64, + + // New parallel computing configuration + parallel: { + // Master enable/disable switch + enabled: true, + + // Automatic backend selection based on operation and size + autoSelect: true, + + // Backend-specific configuration + backends: { + // Web Workers configuration + workers: { + enabled: true, + maxWorkers: navigator.hardwareConcurrency || 4, + minTaskSize: { + matrix: 100, // Minimum matrix dimension + array: 1000 // Minimum array length + } + }, + + // WebAssembly configuration + wasm: { + enabled: true, + simd: true, // Enable SIMD when available + threads: true, // Enable threading when available + relaxedSimd: false // Relaxed SIMD (experimental) + }, + + // WebGPU configuration + webgpu: { + enabled: true, + powerPreference: 'high-performance', // or 'low-power' + minDimension: 256, // Minimum matrix size for GPU + minElements: 100000 // Minimum array size for GPU + } + }, + + // Operations to parallelize (or '*' for all supported) + operations: ['multiply', 'add', 'subtract', 'det', 'inv', 'eigs', + 'mean', 'std', 'variance', 'median', 'map', 'filter'], + + // Graceful degradation on errors + fallbackToSerial: true, + + // Performance monitoring + collectMetrics: false + } +}); +``` + +### Factory Modification Pattern + +Matrix operations integrate parallel implementations through the factory pattern: + +```javascript +// Modified factory for parallel-capable matrix multiplication +export const createMultiply = factory(name, dependencies, ({ + typed, + matrix, + config, + parallelDispatcher // Injected parallel dispatcher +}) => { + + const multiply = typed(name, { + // Original signatures preserved for backward compatibility + 'number, number': (x, y) => x * y, + 'Complex, Complex': multiplyComplex, + + // Enhanced Matrix signature with parallel dispatch + 'Matrix, Matrix': function (x, y) { + const xSize = x.size(); + const ySize = y.size(); + + // Validate dimensions + if (xSize[1] !== ySize[0]) { + throw new DimensionError(xSize[1], ySize[0]); + } + + // Check if parallel processing should be used + if (parallelDispatcher && + parallelDispatcher.shouldParallelize('matrix.multiply', x, y)) { + return parallelDispatcher.execute('matrix.multiply', { x, y }); + } + + // Fallback to existing serial implementation + return _multiplyMatrixMatrix(x, y); + }, + + // New explicit parallel API for direct control + 'Matrix, Matrix, Object': function (x, y, options) { + if (options.parallel === false) { + return _multiplyMatrixMatrix(x, y); + } + + const backend = options.backend || 'auto'; + return parallelDispatcher.execute('matrix.multiply', { x, y }, { + forceBackend: backend + }); + } + }); + + // Attach metadata for introspection + multiply.parallel = { + supported: true, + backends: ['webgpu', 'wasm-simd', 'wasm', 'workers'], + minSize: { matrix: 50 } + }; + + return multiply; +}); +``` + +--- + +## Implementation Roadmap Prioritizes Incremental Adoption + +### Phase 1: Foundation and Web Workers (Weeks 1-4) + +Phase 1 establishes the parallel processing infrastructure with Web Workers, targeting the most problematic operations like matrix multiplication and determinant calculation. + +**Deliverables:** +- Worker pool management system in `src/core/parallel/WorkerPool.js` +- SharedArrayBuffer support with Transferable fallback +- Parallel matrix multiplication using block decomposition +- Configuration system integration +- Comprehensive test suite for parallel correctness + +**Target Performance:** +- 2-4x speedup for matrix operations above 100×100 +- No regression for operations below parallel thresholds +- All existing tests continue to pass + +### Phase 2: WebAssembly Integration (Weeks 5-8) + +Phase 2 introduces WebAssembly acceleration with SIMD for computationally intensive algorithms. + +**Deliverables:** +- AssemblyScript kernel implementation in `src/core/wasm/kernels/` +- SIMD-optimized matrix operations +- Statistical functions (sum, mean, variance, std) +- Build system integration for WASM compilation +- Automatic feature detection and fallback + +**Target Performance:** +- 5-10x speedup with SIMD for array operations +- 8-15x speedup for matrix multiplication +- Binary size under 50KB gzipped for WASM modules + +### Phase 3: WebGPU Acceleration (Weeks 9-12) + +Phase 3 adds WebGPU compute shader support for massive parallelism on GPU-capable systems. + +**Deliverables:** +- WebGPU compute engine in `src/core/gpu/WebGPUCompute.js` +- WGSL shader library for matrix and statistical operations +- Pipeline caching for performance +- Memory management with automatic buffer pooling +- Graceful degradation when WebGPU unavailable + +**Target Performance:** +- 50-100x speedup for large matrices (1000×1000+) +- 20-50x speedup for large array operations (1M+ elements) +- Sub-100ms latency for most operations + +### Phase 4: Optimization and Integration (Weeks 13-16) + +Phase 4 focuses on system optimization, comprehensive testing, and documentation. + +**Deliverables:** +- Unified parallel dispatcher with intelligent backend selection +- Performance benchmark suite +- Memory optimization and pooling +- Complete documentation and migration guides +- Browser compatibility testing across all supported platforms + +--- + +## Memory Management Strategies Require Careful Consideration + +### Zero-Copy Data Sharing Architecture + +```javascript +// Unified memory management across parallel backends +class ParallelMemoryManager { + constructor(config) { + this.config = config; + this.pools = { + shared: new SharedBufferPool(), + gpu: new GPUBufferPool(), + wasm: new WasmMemoryPool() + }; + } + + // Acquire buffer optimized for target backend + acquire(size, backend, options = {}) { + const alignment = options.alignment || 8; + const alignedSize = Math.ceil(size / alignment) * alignment; + + switch (backend) { + case 'workers': + return this.pools.shared.acquire(alignedSize); + case 'webgpu': + return this.pools.gpu.acquire(alignedSize); + case 'wasm': + return this.pools.wasm.acquire(alignedSize); + default: + return new ArrayBuffer(alignedSize); + } + } + + release(buffer, backend) { + const pool = this.pools[this.getPoolKey(backend)]; + if (pool) { + pool.release(buffer); + } + } +} + +// SharedArrayBuffer pool for Web Worker data sharing +class SharedBufferPool { + constructor(maxPoolSize = 100 * 1024 * 1024) { // 100MB pool + this.maxSize = maxPoolSize; + this.currentSize = 0; + this.available = new Map(); // size -> [buffers] + this.inUse = new Set(); + } + + acquire(size) { + // Round up to power of 2 for better pool utilization + const bucketSize = this.nextPowerOf2(size); + + if (this.available.has(bucketSize)) { + const buffers = this.available.get(bucketSize); + if (buffers.length > 0) { + const buffer = buffers.pop(); + this.inUse.add(buffer); + return buffer; + } + } + + // Allocate new buffer + const buffer = new SharedArrayBuffer(bucketSize); + this.currentSize += bucketSize; + this.inUse.add(buffer); + + // Evict old buffers if pool is too large + if (this.currentSize > this.maxSize) { + this.evict(bucketSize); + } + + return buffer; + } + + release(buffer) { + if (!this.inUse.has(buffer)) return; + + this.inUse.delete(buffer); + const size = buffer.byteLength; + + if (!this.available.has(size)) { + this.available.set(size, []); + } + this.available.get(size).push(buffer); + } + + nextPowerOf2(n) { + return Math.pow(2, Math.ceil(Math.log2(n))); + } +} +``` + +### Memory Limits and Platform Considerations + +| Platform | SharedArrayBuffer | WebGPU Buffer | WASM Linear Memory | +|----------|------------------|---------------|-------------------| +| Desktop Chrome | 2GB | 2GB | 4GB | +| Desktop Firefox | 2GB | 2GB | 4GB | +| Desktop Safari | 2GB | 256MB-1GB | 4GB | +| Mobile Chrome | 512MB | 128MB-256MB | 1GB | +| Mobile Safari | 256MB | 128MB | 1GB | + +The parallel computing implementation respects these limits with conservative defaults and provides configuration options for tuning. + +--- + +## Browser Compatibility and Feature Detection + +### Current Browser Support Matrix (November 2025) + +| Feature | Chrome | Firefox | Safari | Edge | +|---------|--------|---------|--------|------| +| Web Workers | ✅ All | ✅ All | ✅ All | ✅ All | +| SharedArrayBuffer | ✅ 68+ | ✅ 79+ | ✅ 15.2+ | ✅ 79+ | +| WebAssembly | ✅ 57+ | ✅ 52+ | ✅ 11+ | ✅ 16+ | +| WASM SIMD | ✅ 91+ | ✅ 89+ | ✅ 16.4+ | ✅ 91+ | +| WASM Threads | ✅ 74+ | ✅ 79+ | ✅ 14.1+ | ✅ 79+ | +| Relaxed SIMD | ✅ 114+ | 🟡 Flag | 🟡 Flag | ✅ 114+ | +| WebGPU | ✅ 113+ | ✅ 141+ | ✅ 26+ | ✅ 113+ | + +### Runtime Feature Detection + +```javascript +// Comprehensive feature detection for Math.js parallel computing +const detectParallelCapabilities = async () => { + const capabilities = { + workers: typeof Worker !== 'undefined', + sharedArrayBuffer: typeof SharedArrayBuffer !== 'undefined', + atomics: typeof Atomics !== 'undefined', + crossOriginIsolated: typeof crossOriginIsolated !== 'undefined' + && crossOriginIsolated, + transferable: true, + + webassembly: typeof WebAssembly !== 'undefined', + wasmSimd: false, + wasmThreads: false, + wasmRelaxedSimd: false, + + webgpu: false, + webgpuTimestampQuery: false + }; + + // Test WASM SIMD + if (capabilities.webassembly) { + try { + // Minimal SIMD test module + const simdTest = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7b, 0x03, + 0x02, 0x01, 0x00, 0x0a, 0x0a, 0x01, 0x08, 0x00, + 0x41, 0x00, 0xfd, 0x0f, 0x0b + ]); + await WebAssembly.compile(simdTest); + capabilities.wasmSimd = true; + } catch (e) {} + + // Test WASM Threads + capabilities.wasmThreads = capabilities.sharedArrayBuffer; + } + + // Test WebGPU + if (typeof navigator !== 'undefined' && navigator.gpu) { + try { + const adapter = await navigator.gpu.requestAdapter(); + if (adapter) { + capabilities.webgpu = true; + capabilities.webgpuTimestampQuery = + adapter.features.has('timestamp-query'); + } + } catch (e) {} + } + + return capabilities; +}; +``` + +--- + +## Performance Benchmarks and Optimization Targets + +### Target Performance Improvements + +| Operation | Current | Target (Workers) | Target (WASM+SIMD) | Target (WebGPU) | +|-----------|---------|------------------|-------------------|-----------------| +| Matrix Multiply 100×100 | 120ms | 45ms (2.7x) | 15ms (8x) | N/A* | +| Matrix Multiply 500×500 | 15s | 4.5s (3.3x) | 1.2s (12.5x) | 80ms (187x) | +| Matrix Multiply 1000×1000 | 120s | 35s (3.4x) | 8s (15x) | 200ms (600x) | +| Determinant 200×200 | 8s | 2.5s (3.2x) | 800ms (10x) | 50ms (160x) | +| Eigenvalues 300×300 | 45s | 14s (3.2x) | 4s (11x) | 400ms (112x) | +| Mean (10M elements) | 80ms | 25ms (3.2x) | 8ms (10x) | 5ms (16x) | +| Std Dev (10M elements) | 150ms | 45ms (3.3x) | 15ms (10x) | 8ms (19x) | + +*WebGPU not beneficial for small matrices due to transfer overhead + +### Benchmark Methodology + +```javascript +// Performance benchmark framework +class ParallelBenchmark { + constructor(math, iterations = 100) { + this.math = math; + this.iterations = iterations; + this.results = []; + } + + async runMatrixMultiplyBenchmark(sizes = [100, 256, 500, 1000]) { + const results = {}; + + for (const size of sizes) { + // Generate random matrices + const A = this.math.random([size, size]); + const B = this.math.random([size, size]); + + results[size] = { + serial: await this.benchmark(() => this.math.multiply(A, B), + { parallel: { enabled: false } }), + workers: await this.benchmark(() => this.math.multiply(A, B), + { parallel: { backends: { workers: { enabled: true } } } }), + wasm: await this.benchmark(() => this.math.multiply(A, B), + { parallel: { backends: { wasm: { enabled: true } } } }), + webgpu: await this.benchmark(() => this.math.multiply(A, B), + { parallel: { backends: { webgpu: { enabled: true } } } }) + }; + } + + return results; + } + + async benchmark(fn, config) { + // Configure math.js + const originalConfig = this.math.config(); + this.math.config(config); + + // Warmup + for (let i = 0; i < 5; i++) await fn(); + + // Timed runs + const times = []; + for (let i = 0; i < this.iterations; i++) { + const start = performance.now(); + await fn(); + times.push(performance.now() - start); + } + + // Restore config + this.math.config(originalConfig); + + // Calculate statistics + return { + mean: times.reduce((a, b) => a + b) / times.length, + median: times.sort((a, b) => a - b)[Math.floor(times.length / 2)], + min: Math.min(...times), + max: Math.max(...times), + stdDev: Math.sqrt( + times.reduce((sq, t) => sq + Math.pow(t - mean, 2), 0) / times.length + ) + }; + } +} +``` + +--- + +## Distribution Strategy Adapts to Parallel Capabilities + +### Build Variants + +The refactored Math.js produces multiple distribution bundles: + +```javascript +// Build configuration producing multiple variants +module.exports = { + entry: { + // Standard bundle with all parallel features + 'mathjs': './src/index.js', + + // Parallel-only entry for explicit parallel imports + 'mathjs.parallel': './src/core/parallel/index.js', + + // Lightweight bundle without parallel features + 'mathjs.lite': './src/index-lite.js' + }, + + output: { + path: path.resolve(__dirname, 'dist'), + filename: '[name].js', + library: { + name: 'math', + type: 'umd', + export: 'default' + } + }, + + // Separate WASM and worker files for lazy loading + experiments: { + asyncWebAssembly: true + } +}; +``` + +### Bundle Sizes + +| Bundle | Size (minified) | Size (gzipped) | +|--------|-----------------|----------------| +| mathjs.js | 180KB | 58KB | +| mathjs.parallel.js | 45KB | 14KB | +| mathjs.lite.js | 140KB | 45KB | +| math-kernels.wasm | 35KB | 12KB | +| math-kernels.simd.wasm | 42KB | 14KB | + +### Lazy Loading Strategy + +```javascript +// Lazy loading of parallel computing modules +const math = create(all); + +// Parallel features loaded on-demand +math.parallel = { + async initialize() { + // Dynamically import parallel module + const { ParallelDispatcher } = await import('./parallel/index.js'); + this._dispatcher = new ParallelDispatcher(); + return this._dispatcher.initialize(); + }, + + get initialized() { + return this._dispatcher?.initialized ?? false; + } +}; + +// WebAssembly loaded when first needed +let wasmLoader = null; +const getWasmLoader = async () => { + if (!wasmLoader) { + const { WasmLoader } = await import('./wasm/WasmLoader.js'); + wasmLoader = new WasmLoader(); + await wasmLoader.initialize(); + } + return wasmLoader; +}; +``` + +--- + +## Conclusion + +Math.js's factory-based architecture provides excellent foundations for parallel processing integration without breaking changes. The identified performance bottlenecks in matrix operations and expression evaluation present clear optimization targets that align with proven Web Workers, WebAssembly, and WebGPU patterns. + +### Key Technical Achievements + +The parallel computing integration delivers transformative performance improvements across three complementary technologies: + +**Web Workers** provide 2-4x speedup for CPU-bound operations through multi-threaded execution, with SharedArrayBuffer enabling zero-copy data sharing between threads. This technology is mature, widely supported, and serves as the reliable fallback for all parallel operations. + +**WebAssembly with SIMD** delivers 5-15x speedup for numeric computations through near-native execution speed and vectorized operations. The 128-bit SIMD registers process multiple data elements simultaneously, dramatically accelerating matrix operations and statistical calculations. + +**WebGPU** enables 50-500x speedup for large-scale operations through massively parallel GPU compute shaders. With cross-browser support now achieved in Chrome, Firefox, and Safari, WebGPU represents the future of high-performance web computing and enables Math.js to compete with native numerical libraries for large matrices. + +### Implementation Strategy + +The phased implementation approach ensures stability while delivering incremental value: + +1. **Foundation Phase**: Worker pool infrastructure with SharedArrayBuffer optimization +2. **Acceleration Phase**: WebAssembly integration with SIMD-optimized kernels +3. **GPU Phase**: WebGPU compute shaders for massive parallelism +4. **Integration Phase**: Unified dispatcher with intelligent backend selection + +### Backward Compatibility Guarantee + +The refactoring maintains complete backward compatibility through: +- Preserved API surface with no breaking changes +- Automatic parallel dispatch based on operation characteristics +- Graceful degradation when parallel features unavailable +- Configuration-driven enablement allowing explicit control + +### Performance Targets Summary + +| Scenario | Current Performance | Target Performance | Improvement | +|----------|--------------------|--------------------|-------------| +| Matrix Multiply 1000×1000 | ~120 seconds | ~200 milliseconds | 600x | +| Large Array Statistics (10M) | ~150 milliseconds | ~8 milliseconds | 19x | +| Complex Expression Evaluation | ~80 milliseconds | ~25 milliseconds | 3.2x | +| Eigenvalue Computation 300×300 | ~45 seconds | ~400 milliseconds | 112x | + +The Math.js parallel computing refactoring transforms the library from a capable but slow mathematical toolkit into a high-performance computational engine suitable for demanding applications in data science, machine learning, scientific computing, and real-time visualization—all while maintaining the simplicity and accessibility that define the Math.js developer experience. + +--- + +## References and Further Reading + +1. WebGPU Specification: https://www.w3.org/TR/webgpu/ +2. WebAssembly SIMD Proposal: https://github.com/WebAssembly/simd +3. SharedArrayBuffer Security: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer +4. AssemblyScript Documentation: https://www.assemblyscript.org/ +5. Math.js GitHub Repository: https://github.com/josdejong/mathjs +6. TensorFlow.js WASM Backend: https://www.tensorflow.org/js/guide/platform_environment +7. Chrome WebGPU Documentation: https://developer.chrome.com/docs/capabilities/web-apis/gpu-compute diff --git a/PHASE_3_PROBABILITY_DISTRIBUTIONS_CONTINUOUS.md b/PHASE_3_PROBABILITY_DISTRIBUTIONS_CONTINUOUS.md new file mode 100644 index 0000000000..6a2df9d0e6 --- /dev/null +++ b/PHASE_3_PROBABILITY_DISTRIBUTIONS_CONTINUOUS.md @@ -0,0 +1,3456 @@ +# Phase 3: Probability Distributions (Part 1 - Continuous) + +**Complete Implementation Guide with Algorithms, Formulas, and Pseudocode** + +--- + +## Overview + +This document provides detailed mathematical formulas, numerical algorithms, approximation coefficients, and complete implementation pseudocode for 10 continuous probability distributions. Each distribution includes PDF, CDF, inverse CDF (quantile function), and random sampling methods optimized for numerical stability and performance. + +**Performance Targets:** +- JavaScript baseline: Current performance +- WASM: 3-8x speedup for complex distributions +- Parallel: Additional 2-3x for batch operations + +**Numerical Precision:** +- Relative error < 10⁻¹² for double precision +- Special handling for extreme parameter values +- Graceful degradation for edge cases + +--- + +## Task 3.1: Normal Distribution + +### Mathematical Foundation + +**Probability Density Function (PDF):** +``` +φ(x; μ, σ) = (1 / (σ√(2π))) * exp(-(x - μ)² / (2σ²)) +``` + +Where: +- μ = mean (location parameter) +- σ = standard deviation (scale parameter, σ > 0) +- Standard normal: μ = 0, σ = 1 + +**Cumulative Distribution Function (CDF):** +``` +Φ(x; μ, σ) = (1/2) * [1 + erf((x - μ) / (σ√2))] +``` + +Standard normal CDF: +``` +Φ(z) = (1/2) * [1 + erf(z / √2)] +``` + +### Inverse CDF (Quantile Function) + +**Rational Approximation Method (Abramowitz & Stegun 26.2.23)** + +For 0 < p < 1, compute Φ⁻¹(p): + +**Algorithm:** +1. If p > 0.5, use symmetry: Φ⁻¹(p) = -Φ⁻¹(1 - p) +2. For 0 < p ≤ 0.5: + +``` +t = sqrt(-2 * ln(p)) + +numerator = c₀ + c₁*t + c₂*t² +denominator = 1 + d₁*t + d₂*t² + d₃*t³ + +x = t - numerator / denominator +``` + +**Coefficients (Abramowitz & Stegun):** +``` +c₀ = 2.515517 +c₁ = 0.802853 +c₂ = 0.010328 + +d₁ = 1.432788 +d₂ = 0.189269 +d₃ = 0.001308 +``` + +**Refined Method (Acklam's Algorithm for higher precision):** + +For p ∈ (0.02425, 0.97575): +``` +q = p - 0.5 +r = q² + +x = q * (a₀ + a₁*r + a₂*r² + a₃*r³ + a₄*r⁴ + a₅*r⁵) / + (1 + b₁*r + b₂*r² + b₃*r³ + b₄*r⁴ + b₅*r⁵) +``` + +**Acklam Coefficients:** +``` +a₀ = -3.969683028665376e+01 +a₁ = 2.209460984245205e+02 +a₂ = -2.759285104469687e+02 +a₃ = 1.383577518672690e+02 +a₄ = -3.066479806614716e+01 +a₅ = 2.506628277459239e+00 + +b₁ = -5.447609879822406e+01 +b₂ = 1.615858368580409e+02 +b₃ = -1.556989798598866e+02 +b₄ = 6.680131188771972e+01 +b₅ = -1.328068155288572e+01 +``` + +For tails (p < 0.02425 or p > 0.97575): +``` +if p < 0.5: + q = sqrt(-2 * ln(p)) +else: + q = sqrt(-2 * ln(1 - p)) + +x = (c₀ + c₁*q + c₂*q² + c₃*q³ + c₄*q⁴ + c₅*q⁵ + c₆*q⁶) / + (1 + d₁*q + d₂*q² + d₃*q³ + d₄*q⁴ + d₅*q⁵ + d₆*q⁶) + +if p > 0.5: x = -x +``` + +**Tail Coefficients:** +``` +c₀ = -7.784894002430293e-03 +c₁ = -3.223964580411365e-01 +c₂ = -2.400758277161838e+00 +c₃ = -2.549732539343734e+00 +c₄ = 4.374664141464968e+00 +c₅ = 2.938163982698783e+00 +c₆ = 0.0 + +d₁ = 7.784695709041462e-03 +d₂ = 3.224671290700398e-01 +d₃ = 2.445134137142996e+00 +d₄ = 3.754408661907416e+00 +d₅ = 0.0 +d₆ = 0.0 +``` + +### Random Sampling + +**Method 1: Box-Muller Transform** + +Generates two independent standard normal variates from two uniform variates. + +``` +Input: U₁, U₂ ~ Uniform(0, 1) +Output: Z₁, Z₂ ~ Normal(0, 1) + +Algorithm: + R = sqrt(-2 * ln(U₁)) + θ = 2π * U₂ + + Z₁ = R * cos(θ) + Z₂ = R * sin(θ) + + # Transform to N(μ, σ²): + X₁ = μ + σ * Z₁ + X₂ = μ + σ * Z₂ +``` + +**Method 2: Marsaglia Polar Method (improved Box-Muller)** + +Avoids trigonometric functions: + +``` +Algorithm: + repeat: + U₁ = Uniform(-1, 1) + U₂ = Uniform(-1, 1) + S = U₁² + U₂² + until S < 1 and S > 0 + + multiplier = sqrt(-2 * ln(S) / S) + Z₁ = U₁ * multiplier + Z₂ = U₂ * multiplier + + X₁ = μ + σ * Z₁ + X₂ = μ + σ * Z₂ +``` + +**Method 3: Ziggurat Algorithm (fastest)** + +Rejection sampling with optimized rectangular layers. + +``` +Constants: + R = 3.442619855899 // Right tail start + V = 0.00991256303526217 // Area under each rectangle + N = 128 // Number of layers + + // Precomputed tables (x[], y[], k[]) + x[0] = 3.7130862467403632609 + x[i] = sqrt(-2 * ln(V / x[i-1] + exp(-x[i-1]² / 2))) for i = 1..N-1 + + y[i] = exp(-x[i]² / 2) + + k[i] = floor((x[i-1] / x[i]) * 2³²) + +Algorithm ziggurat_normal(): + loop: + # Generate random 32-bit integer + j = random_uint32() + + # Extract index (7 bits) and sign + i = j & 0x7F # Low 7 bits + sign = j & 0x80 # 8th bit + + # Generate U in [0, 1) + U = 0.5 + (j >> 8) * (1.0 / 2²⁴) + + # Generate candidate x + x = U * x[i] + + # Fast acceptance test + if j < k[i]: + return sign ? -x : x + + # Layer 0: tail + if i == 0: + loop: + x = -ln(uniform()) / R + y = -ln(uniform()) + if 2*y > x²: + return sign ? -(R + x) : (R + x) + + # Slow acceptance test + if y[i+1] + uniform() * (y[i] - y[i+1]) < exp(-x² / 2): + return sign ? -x : x +``` + +**Ziggurat Precomputed Tables (N=128):** + +```c +// x values (layer boundaries) +static const double x[129] = { + 3.7130862467403632609, 3.4426198558099, 3.2230849845786997, + 3.0832976611832, 2.9776639359279, 2.8919132923036, + 2.8195413982051, 2.7571960119204, 2.7025010004547, + 2.6539306039671, 2.6104503380391, 2.5712321208367, + 2.5356671247124, 2.5032889159989, 2.4736903611401, + 2.4465249987369, 2.4215024758456, 2.3983742193382, + // ... (complete table omitted for brevity) + 0.0 +}; + +// y values (exp(-x²/2)) +static const double y[129] = { + 0.0, 0.00099125630352622177, 0.0019798283941624038, + // ... (complete table) +}; + +// k values (floor((x[i-1]/x[i]) * 2^32)) +static const uint32_t k[128] = { + 0, 12590644, 14272753, 14988545, 15384333, + // ... (complete table) +}; +``` + +### Complete Pseudocode + +```javascript +class NormalDistribution { + constructor(mu = 0, sigma = 1) { + this.mu = mu + this.sigma = sigma + this.spare = null // For Box-Muller + } + + pdf(x) { + const z = (x - this.mu) / this.sigma + return (1 / (this.sigma * sqrt(2 * PI))) * + exp(-z * z / 2) + } + + cdf(x) { + const z = (x - this.mu) / (this.sigma * SQRT2) + return 0.5 * (1 + erf(z)) + } + + quantile(p) { + if (p <= 0 || p >= 1) { + throw new Error("p must be in (0, 1)") + } + + let x + + // Acklam's algorithm + if (p >= 0.02425 && p <= 0.97575) { + // Central region + const q = p - 0.5 + const r = q * q + x = q * this.rational(r, ACKLAM_A, ACKLAM_B) + } else { + // Tails + const q = p < 0.5 ? sqrt(-2 * ln(p)) : sqrt(-2 * ln(1 - p)) + x = this.rational(q, ACKLAM_C, ACKLAM_D) + if (p > 0.5) x = -x + } + + // Refine with one Newton iteration + const e = this.cdf(x) - p + const u = e * sqrt(2 * PI) * exp(x * x / 2) + x = x - u + + return this.mu + this.sigma * x + } + + rational(t, num_coeffs, den_coeffs) { + let num = num_coeffs[0] + let den = 1 + + for (let i = 1; i < num_coeffs.length; i++) { + num = num * t + num_coeffs[i] + } + + for (let i = 0; i < den_coeffs.length; i++) { + den = den * t + den_coeffs[i] + } + + return num / den + } + + random() { + // Box-Muller with spare value caching + if (this.spare !== null) { + const val = this.spare + this.spare = null + return this.mu + this.sigma * val + } + + let u, v, s + do { + u = 2 * Math.random() - 1 + v = 2 * Math.random() - 1 + s = u * u + v * v + } while (s >= 1 || s === 0) + + const multiplier = sqrt(-2 * ln(s) / s) + this.spare = v * multiplier + return this.mu + this.sigma * u * multiplier + } +} +``` + +--- + +## Task 3.2: Student's t Distribution + +### Mathematical Foundation + +**Probability Density Function:** +``` +f(x; ν) = [Γ((ν+1)/2) / (√(νπ) * Γ(ν/2))] * (1 + x²/ν)^(-(ν+1)/2) +``` + +Where: +- ν = degrees of freedom (ν > 0) +- Γ = gamma function + +**Cumulative Distribution Function:** + +Using incomplete beta function: +``` +For x ≥ 0: + F(x; ν) = 1 - (1/2) * I(ν/(ν+x²); ν/2, 1/2) + +For x < 0: + F(x; ν) = (1/2) * I(ν/(ν+x²); ν/2, 1/2) +``` + +Where I(z; a, b) is the regularized incomplete beta function. + +**Alternative CDF formulation:** +``` +F(x; ν) = 1/2 + x * Γ((ν+1)/2) / (√(νπ) * Γ(ν/2)) * + ₂F₁(1/2, (ν+1)/2; 3/2; -x²/ν) +``` + +### Inverse CDF (Quantile Function) + +**Hill's Algorithm (1970) with Newton refinement:** + +``` +Input: p ∈ (0, 1), ν > 0 +Output: t such that F(t; ν) = p + +Algorithm: + # Use normal approximation for large ν + if ν > 1000: + return normal_quantile(p) + + # Initial approximation + if ν == 1: # Cauchy distribution + return tan(π * (p - 0.5)) + + if ν == 2: + return sqrt(2) * (2*p - 1) / sqrt(2*p*(1-p)) + + # For general ν, use Wilson-Hilferty approximation + a = 1 / (ν - 0.5) + b = 48 / (a * a) + c = ((20700 * a / b - 98) * a - 16) * a + 96.36 + d = ((94.5 / (b + c) - 3) / b + 1) * sqrt(a * PI / 2) * ν + + z = normal_quantile(p) + y = z^(2/ν) + + if y > 0.05 + a: + # Large y + x = normal_quantile(0.5 * (1 + sign(p - 0.5))) + y = x * x + + if ν < 5: + c = c + 0.3 * (ν - 4.5) * (x + 0.6) + + c = (((0.05 * d * x - 5) * x - 7) * x - 2) * x + b + c + y = (((((0.4 * y + 6.3) * y + 36) * y + 94.5) / c - y - 3) / b + 1) * x + y = a * y * y + + if y > 0.002: + y = exp(y) - 1 + else: + y = 0.5 * y * y + y + else: + # Small y + y = ((1 / (((ν + 6) / (ν * y) - 0.089 * d - 0.822) * + (ν + 2) * 3) + 0.5 / (ν + 4)) * y - 1) * + (ν + 1) / (ν + 2) + 1 / y + + t = sqrt(ν * y) + if p < 0.5: + t = -t + + # Newton refinement (1-2 iterations) + for iter in 1..2: + error = cdf(t, ν) - p + if abs(error) < 1e-14: + break + derivative = pdf(t, ν) + t = t - error / derivative + + return t +``` + +**Alternative: Beta approximation for small ν:** + +``` +For ν ≤ 20: + if p > 0.5: + q = 2 * (1 - p) + sign = 1 + else: + q = 2 * p + sign = -1 + + # Solve using incomplete beta inverse + beta_val = inverse_beta(q, ν/2, 0.5) + t = sign * sqrt(ν * (1 - beta_val) / beta_val) + + return t +``` + +### Random Sampling + +**Method 1: Ratio of Normal and Chi-Square** + +Based on the definition: T = Z / sqrt(V/ν), where Z ~ N(0,1) and V ~ χ²(ν) + +``` +Algorithm t_random(ν): + Z = standard_normal() + V = chi_square(ν) + + return Z / sqrt(V / ν) +``` + +**Method 2: Direct via Chi-Square (more efficient)** + +``` +Algorithm t_random(ν): + Z = standard_normal() + + # Generate χ² using gamma + U = gamma(ν/2, 2) # χ²(ν) = Gamma(ν/2, 2) + + return Z * sqrt(ν / U) +``` + +**Method 3: Kinderman-Monahan-Ramage (1977) - fastest** + +Optimized rejection sampling for ν ≥ 3: + +``` +Algorithm KMR_t_random(ν): + c = sqrt((ν + 1) / ν) + + loop: + U = uniform(0, 1) + V = uniform(0, 1) + + W = (2*U - 1) / V + + if W² ≤ (ν + 1) * (1 - V) / (ν + W² * V): + return c * W +``` + +### Complete Pseudocode + +```javascript +class StudentTDistribution { + constructor(degreesOfFreedom) { + if (degreesOfFreedom <= 0) { + throw new Error("Degrees of freedom must be positive") + } + this.nu = degreesOfFreedom + this.normalDist = new NormalDistribution(0, 1) + } + + pdf(x) { + const nu = this.nu + const coefficient = gamma((nu + 1) / 2) / + (sqrt(nu * PI) * gamma(nu / 2)) + return coefficient * pow(1 + x*x / nu, -(nu + 1) / 2) + } + + cdf(x) { + const nu = this.nu + + if (nu === 1) { + // Cauchy distribution + return 0.5 + atan(x) / PI + } + + if (nu === 2) { + return 0.5 * (1 + x / sqrt(2 + x*x)) + } + + // General case using incomplete beta + const t2 = x * x + const beta_arg = nu / (nu + t2) + const I_val = incompleteBeta(beta_arg, nu/2, 0.5) + + if (x >= 0) { + return 1 - 0.5 * I_val + } else { + return 0.5 * I_val + } + } + + quantile(p) { + if (p <= 0 || p >= 1) { + throw new Error("p must be in (0, 1)") + } + + const nu = this.nu + + // Use symmetry + const sign = p < 0.5 ? -1 : 1 + const p_adj = p < 0.5 ? 2*p : 2*(1-p) + + // Special cases + if (nu === 1) { + return tan(PI * (p - 0.5)) + } + + if (nu === 2) { + return sqrt(2) * (2*p - 1) / sqrt(2*p*(1-p)) + } + + // Large nu: use normal approximation + if (nu > 1000) { + return this.normalDist.quantile(p) + } + + // Hill's algorithm (implemented above) + let t = this.hillAlgorithm(p, nu) + + // Newton refinement + for (let i = 0; i < 2; i++) { + const err = this.cdf(t) - p + if (Math.abs(err) < 1e-14) break + t = t - err / this.pdf(t) + } + + return t + } + + random() { + if (this.nu >= 3) { + // KMR method + const c = sqrt((this.nu + 1) / this.nu) + + while (true) { + const U = Math.random() + const V = Math.random() + const W = (2*U - 1) / V + + if (W*W <= (this.nu + 1) * (1 - V) / (this.nu + W*W*V)) { + return c * W + } + } + } else { + // Ratio method + const Z = this.normalDist.random() + const U = gamma_random(this.nu / 2, 2) + return Z * sqrt(this.nu / U) + } + } +} +``` + +--- + +## Task 3.3: Chi-Square Distribution + +### Mathematical Foundation + +The chi-square distribution is a special case of the gamma distribution: +``` +χ²(k) = Gamma(k/2, 2) +``` + +**Probability Density Function:** +``` +f(x; k) = (1 / (2^(k/2) * Γ(k/2))) * x^(k/2 - 1) * exp(-x/2) +``` + +For x > 0, where k = degrees of freedom (k > 0). + +**Standard form:** +``` +f(x; k) = x^(k/2 - 1) * exp(-x/2) / (2^(k/2) * Γ(k/2)) +``` + +**Cumulative Distribution Function:** +``` +F(x; k) = γ(k/2, x/2) / Γ(k/2) = P(k/2, x/2) +``` + +Where: +- γ(a, x) = lower incomplete gamma function +- P(a, x) = regularized lower incomplete gamma function + +### Inverse CDF (Quantile Function) + +**Wilson-Hilferty Approximation (initial guess):** + +``` +For k ≥ 1: + z = normal_quantile(p) + x₀ = k * (1 - 2/(9*k) + z * sqrt(2/(9*k)))³ +``` + +**Newton-Raphson Refinement:** + +``` +Algorithm chi_square_quantile(p, k): + # Initial guess + z = normal_quantile(p) + + if k == 1: + # Special case: χ²(1) = Z² + return z * z + + if k == 2: + # Special case: χ²(2) = -2*ln(1-p) + return -2 * ln(1 - p) + + # Wilson-Hilferty approximation + h = 2 / (9 * k) + x = k * pow(1 - h + z * sqrt(h), 3) + + # Ensure x > 0 + if x <= 0: + x = 0.001 + + # Newton-Raphson iterations + max_iter = 10 + for i in 1..max_iter: + # Current CDF value + F_x = lower_incomplete_gamma(k/2, x/2) / gamma(k/2) + + # Error + error = F_x - p + + if abs(error) < 1e-12: + break + + # Derivative (PDF) + f_x = pow(x, k/2 - 1) * exp(-x/2) / (pow(2, k/2) * gamma(k/2)) + + # Newton update + x = x - error / f_x + + # Keep x positive + if x <= 0: + x = 0.001 + + return x +``` + +**Alternative: Direct incomplete gamma inversion** + +``` +Algorithm chi_square_quantile_gamma(p, k): + # Use incomplete gamma inverse + y = inverse_incomplete_gamma(k/2, p) + return 2 * y +``` + +### Random Sampling + +**Method 1: Sum of Squared Normals (for small k)** + +By definition: If Z₁, ..., Zₖ ~ N(0,1) independent, then Σᵢ Zᵢ² ~ χ²(k) + +``` +Algorithm chi_square_random_small(k): + if k is not integer: + use gamma method + + sum = 0 + for i in 1..k: + Z = standard_normal() + sum += Z * Z + + return sum +``` + +**Method 2: Gamma Distribution (general, most efficient)** + +``` +Algorithm chi_square_random(k): + # χ²(k) = Gamma(k/2, 2) + return gamma_random(k/2, 2) +``` + +**Method 3: For k = 1 (optimized)** + +``` +Algorithm chi_square_1(): + Z = standard_normal() + return Z * Z +``` + +**Method 4: For k = 2 (exponential)** + +``` +Algorithm chi_square_2(): + # χ²(2) = Exponential(1/2) + return -2 * ln(uniform(0, 1)) +``` + +**Method 5: Non-integer k using gamma** + +For non-integer degrees of freedom: + +``` +Algorithm chi_square_random_nonint(k): + # Use Marsaglia-Tsang gamma + return 2 * gamma_random(k/2, 1) +``` + +### Complete Pseudocode + +```javascript +class ChiSquareDistribution { + constructor(degreesOfFreedom) { + if (degreesOfFreedom <= 0) { + throw new Error("Degrees of freedom must be positive") + } + this.k = degreesOfFreedom + this.gammaShape = degreesOfFreedom / 2 + this.gammaScale = 2 + } + + pdf(x) { + if (x <= 0) return 0 + + const k = this.k + const coeff = 1 / (pow(2, k/2) * gamma(k/2)) + return coeff * pow(x, k/2 - 1) * exp(-x/2) + } + + cdf(x) { + if (x <= 0) return 0 + + // P(k/2, x/2) = regularized lower incomplete gamma + return regularizedGammaP(this.k/2, x/2) + } + + quantile(p) { + if (p <= 0 || p >= 1) { + throw new Error("p must be in (0, 1)") + } + + const k = this.k + + // Special cases + if (k === 1) { + const z = normal_quantile(p) + return z * z + } + + if (k === 2) { + return -2 * ln(1 - p) + } + + // Wilson-Hilferty approximation as initial guess + const z = normal_quantile(p) + const h = 2 / (9 * k) + let x = k * pow(1 - h + z * sqrt(h), 3) + + if (x <= 0) x = 0.01 + + // Newton-Raphson refinement + for (let iter = 0; iter < 10; iter++) { + const F_x = this.cdf(x) + const error = F_x - p + + if (Math.abs(error) < 1e-12) break + + const f_x = this.pdf(x) + if (f_x > 0) { + x = x - error / f_x + if (x <= 0) x = 0.01 + } + } + + return x + } + + random() { + // Optimized for special cases + if (this.k === 1) { + const z = standard_normal() + return z * z + } + + if (this.k === 2) { + return -2 * Math.log(1 - Math.random()) + } + + // For small integer k, sum of squared normals + if (this.k <= 10 && Number.isInteger(this.k)) { + let sum = 0 + for (let i = 0; i < this.k; i++) { + const z = standard_normal() + sum += z * z + } + return sum + } + + // General case: use gamma distribution + return gamma_random(this.gammaShape, this.gammaScale) + } +} +``` + +**Relationship to Other Distributions:** + +``` +1. Sum property: χ²(k₁) + χ²(k₂) = χ²(k₁ + k₂) (independent) + +2. Relation to normal: If Z ~ N(0,1), then Z² ~ χ²(1) + +3. Relation to gamma: χ²(k) = Gamma(k/2, 2) + +4. Relation to exponential: χ²(2) = Exponential(1/2) + +5. Central limit theorem: + (χ²(k) - k) / sqrt(2k) → N(0,1) as k → ∞ +``` + +--- + +## Task 3.4: F Distribution + +### Mathematical Foundation + +**Probability Density Function:** + +The F distribution is the ratio of two independent chi-square variables divided by their degrees of freedom: + +``` +f(x; d₁, d₂) = [Γ((d₁+d₂)/2) / (Γ(d₁/2) * Γ(d₂/2))] * + (d₁/d₂)^(d₁/2) * x^(d₁/2 - 1) * + (1 + (d₁/d₂)*x)^(-(d₁+d₂)/2) +``` + +For x > 0, where: +- d₁ = numerator degrees of freedom (d₁ > 0) +- d₂ = denominator degrees of freedom (d₂ > 0) + +**Simplified form:** +``` +f(x; d₁, d₂) = sqrt[(d₁*x)^d₁ * d₂^d₂ / (d₁*x + d₂)^(d₁+d₂)] / (x * B(d₁/2, d₂/2)) +``` + +Where B(a,b) is the beta function. + +**Cumulative Distribution Function:** + +Using the incomplete beta function: + +``` +F(x; d₁, d₂) = I(d₁*x / (d₁*x + d₂); d₁/2, d₂/2) +``` + +Where I(z; a, b) is the regularized incomplete beta function. + +### Inverse CDF (Quantile Function) + +The F quantile can be found by inverting the incomplete beta relationship: + +``` +Algorithm f_quantile(p, d1, d2): + # Special case: d1 = 1, d2 = 1 (special F(1,1)) + if d1 == 1 and d2 == 1: + return tan(PI * p / 2)² + + # Use incomplete beta inverse + # F(x) = I(d1*x/(d1*x+d2); d1/2, d2/2) = p + # Let w = d1*x/(d1*x+d2), then solve I(w; d1/2, d2/2) = p + + w = inverse_incomplete_beta(p, d1/2, d2/2) + + # Solve for x: w = d1*x/(d1*x+d2) + # w*(d1*x + d2) = d1*x + # w*d1*x + w*d2 = d1*x + # w*d2 = d1*x - w*d1*x = d1*x*(1-w) + # x = w*d2 / (d1*(1-w)) + + if w >= 1: + return Infinity + + x = (w * d2) / (d1 * (1 - w)) + + return x +``` + +**Alternative Newton-Raphson approach:** + +``` +Algorithm f_quantile_newton(p, d1, d2): + # Initial guess using Wilson-Hilferty + z = normal_quantile(p) + + # Approximation for initial guess + A = 2 / (9 * d1) + B = 2 / (9 * d2) + + w = (1 - B + z * sqrt(B))³ / (1 - A + z * sqrt(A))³ + + if d2 > 2: + x = w + else: + x = max(0.01, w) + + # Newton iterations + for iter in 1..10: + F_x = cdf(x, d1, d2) + error = F_x - p + + if abs(error) < 1e-12: + break + + f_x = pdf(x, d1, d2) + if f_x > 0: + x = x - error / f_x + x = max(0.001, x) # Keep positive + + return x +``` + +### Random Sampling + +**Method 1: Ratio of Chi-Squares (definition-based)** + +By definition: F(d₁, d₂) = (U₁/d₁) / (U₂/d₂), where U₁ ~ χ²(d₁), U₂ ~ χ²(d₂) + +``` +Algorithm f_random(d1, d2): + U1 = chi_square_random(d1) + U2 = chi_square_random(d2) + + return (U1 / d1) / (U2 / d2) +``` + +**Method 2: Via Gamma (more efficient)** + +Since χ²(k) = Gamma(k/2, 2): + +``` +Algorithm f_random_gamma(d1, d2): + G1 = gamma_random(d1/2, 2) + G2 = gamma_random(d2/2, 2) + + return (G1 / d1) / (G2 / d2) +``` + +**Method 3: Via Beta Distribution** + +Relationship: If X ~ F(d₁, d₂), then Y = d₁X/(d₁X + d₂) ~ Beta(d₁/2, d₂/2) + +``` +Algorithm f_random_beta(d1, d2): + B = beta_random(d1/2, d2/2) + + # Solve for X: B = d1*X/(d1*X + d2) + # B*(d1*X + d2) = d1*X + # B*d1*X + B*d2 = d1*X + # B*d2 = d1*X*(1 - B) + # X = B*d2 / (d1*(1-B)) + + if B >= 1: + return Infinity + + return (B * d2) / (d1 * (1 - B)) +``` + +### Special Cases + +**F(1, d₂) - Relation to t distribution:** +``` +If T ~ t(d₂), then T² ~ F(1, d₂) +``` + +**F(d₁, d₂) reciprocal property:** +``` +If X ~ F(d₁, d₂), then 1/X ~ F(d₂, d₁) +``` + +### Complete Pseudocode + +```javascript +class FDistribution { + constructor(d1, d2) { + if (d1 <= 0 || d2 <= 0) { + throw new Error("Degrees of freedom must be positive") + } + this.d1 = d1 + this.d2 = d2 + } + + pdf(x) { + if (x <= 0) return 0 + + const d1 = this.d1 + const d2 = this.d2 + + const logNumerator = lgamma((d1 + d2) / 2) + + (d1/2) * log(d1/d2) + + (d1/2 - 1) * log(x) + + const logDenominator = lgamma(d1/2) + lgamma(d2/2) + + ((d1 + d2) / 2) * log(1 + d1*x/d2) + + return exp(logNumerator - logDenominator) + } + + cdf(x) { + if (x <= 0) return 0 + + const d1 = this.d1 + const d2 = this.d2 + + // Transform to beta + const w = (d1 * x) / (d1 * x + d2) + + return regularizedIncompleteBeta(w, d1/2, d2/2) + } + + quantile(p) { + if (p <= 0 || p >= 1) { + throw new Error("p must be in (0, 1)") + } + + // Special cases + if (this.d1 === 1 && this.d2 === 1) { + return pow(tan(PI * p / 2), 2) + } + + // Use beta inverse + const w = inverseIncompleteBeta(p, this.d1/2, this.d2/2) + + if (w >= 1 - 1e-15) { + return Infinity + } + + const x = (w * this.d2) / (this.d1 * (1 - w)) + + // Optional: Newton refinement for extreme accuracy + let refined = x + for (let i = 0; i < 2; i++) { + const err = this.cdf(refined) - p + if (Math.abs(err) < 1e-14) break + + const deriv = this.pdf(refined) + if (deriv > 0) { + refined = refined - err / deriv + if (refined <= 0) refined = x + } + } + + return refined + } + + random() { + // Method: ratio of gammas (most efficient) + const G1 = gamma_random(this.d1 / 2, 2) + const G2 = gamma_random(this.d2 / 2, 2) + + return (G1 / this.d1) / (G2 / this.d2) + } +} +``` + +**Numerical Stability Notes:** + +``` +1. For very large d1 or d2 (> 10^6): + - Use asymptotic approximations + - F(d1, ∞) → χ²(d1) / d1 + - F(∞, d2) → d2 / χ²(d2) + +2. For x very large: + - CDF → 1 + - Use complementary formulation for precision + +3. For x very small: + - CDF → 0 + - Direct series expansion may be more accurate +``` + +--- + +## Task 3.5: Beta Distribution + +### Mathematical Foundation + +**Probability Density Function:** +``` +f(x; α, β) = [Γ(α+β) / (Γ(α) * Γ(β))] * x^(α-1) * (1-x)^(β-1) + = x^(α-1) * (1-x)^(β-1) / B(α, β) +``` + +For 0 < x < 1, where: +- α > 0 (shape parameter) +- β > 0 (shape parameter) +- B(α, β) = Γ(α)Γ(β)/Γ(α+β) (beta function) + +**Log PDF (for numerical stability):** +``` +ln f(x; α, β) = (α-1)*ln(x) + (β-1)*ln(1-x) - ln B(α,β) + = (α-1)*ln(x) + (β-1)*ln(1-x) + + lgamma(α+β) - lgamma(α) - lgamma(β) +``` + +**Cumulative Distribution Function:** +``` +F(x; α, β) = I_x(α, β) = B_x(α, β) / B(α, β) +``` + +Where: +- I_x(α, β) = regularized incomplete beta function +- B_x(α, β) = incomplete beta function + +### Inverse CDF (Quantile Function) + +**Newton-Raphson with Initial Guess:** + +``` +Algorithm beta_quantile(p, alpha, beta): + if p <= 0: return 0 + if p >= 1: return 1 + + # Initial guess using moment matching + x0 = initial_guess(p, alpha, beta) + + x = x0 + max_iter = 20 + + for iter in 1..max_iter: + # Current CDF value + F_x = incomplete_beta_reg(x, alpha, beta) + + # Error + error = F_x - p + + if abs(error) < 1e-14 or abs(error) < 1e-10 * p: + break + + # PDF value (derivative) + f_x = pow(x, alpha-1) * pow(1-x, beta-1) / beta_function(alpha, beta) + + # Newton update + dx = -error / f_x + x = x + dx + + # Constrain to (0, 1) + if x <= 0: + x = x0 / 2 + elif x >= 1: + x = (1 + x0) / 2 + + # Final bounds check + if x < 0: x = 0 + if x > 1: x = 1 + + return x + +Function initial_guess(p, alpha, beta): + # Method 1: Normal approximation for large alpha, beta + if alpha > 1 and beta > 1: + mu = alpha / (alpha + beta) + variance = (alpha * beta) / ((alpha + beta)² * (alpha + beta + 1)) + + z = normal_quantile(p) + x = mu + z * sqrt(variance) + + # Constrain + if x < 0.01: x = 0.01 + if x > 0.99: x = 0.99 + + return x + + # Method 2: Simple linear for uniform-like + if abs(alpha - 1) < 0.1 and abs(beta - 1) < 0.1: + return p + + # Method 3: Power function approximation + if alpha < 1 and beta >= 1: + return pow(p * beta_function(alpha, beta), 1/alpha) + + if beta < 1 and alpha >= 1: + return 1 - pow((1-p) * beta_function(alpha, beta), 1/beta) + + # Default: use mean + return alpha / (alpha + beta) +``` + +**Halley's Method (faster convergence):** + +Uses second derivative for cubic convergence: + +``` +Algorithm beta_quantile_halley(p, alpha, beta): + x = initial_guess(p, alpha, beta) + + for iter in 1..10: + # CDF and PDF + F = incomplete_beta_reg(x, alpha, beta) + f = pow(x, alpha-1) * pow(1-x, beta-1) / beta_function(alpha, beta) + + error = F - p + + if abs(error) < 1e-14: + break + + # Second derivative (derivative of PDF) + f_prime = f * ((alpha-1)/x - (beta-1)/(1-x)) + + # Halley update + dx = -error / (f - 0.5 * error * f_prime / f) + + x = x + dx + + # Keep in bounds + x = max(0.001, min(0.999, x)) + + return max(0, min(1, x)) +``` + +### Random Sampling + +**Method 1: Johnk's Algorithm (for α + β < 1)** + +Efficient rejection sampling for small shape parameters: + +``` +Algorithm johnk_beta(alpha, beta): + # Only valid for alpha + beta < 1 + if alpha + beta >= 1: + use different method + + loop: + U = uniform(0, 1) + V = uniform(0, 1) + + X = pow(U, 1/alpha) + Y = pow(V, 1/beta) + + if X + Y <= 1: + return X / (X + Y) +``` + +**Method 2: Cheng's Algorithm (general, most efficient)** + +For α, β > 1 (the most common case): + +``` +Algorithm cheng_beta(alpha, beta): + # Preprocessing + a = min(alpha, beta) + b = max(alpha, beta) + A = a + b + B = sqrt((A - 2) / (2*a*b - A)) + C = a + 1/B + + loop: + U1 = uniform(0, 1) + U2 = uniform(0, 1) + + V = B * ln(U1 / (1 - U1)) + W = a * exp(V) + + # Acceptance test + z = U1² * U2 + r = C*V - ln(4) + s = a + r - W + + if s + 1 + ln(5) >= 5*z: + # Quick accept + X = W / (b + W) + if alpha == a: + return X + else: + return 1 - X + + if s >= ln(z): + # Final accept + X = W / (b + W) + if alpha == a: + return X + else: + return 1 - X +``` + +**Method 3: Ratio of Gammas (always works)** + +Based on the relationship: If X ~ Gamma(α, 1) and Y ~ Gamma(β, 1), then X/(X+Y) ~ Beta(α, β) + +``` +Algorithm beta_random_gamma(alpha, beta): + X = gamma_random(alpha, 1) + Y = gamma_random(beta, 1) + + return X / (X + Y) +``` + +**Method 4: For special cases** + +``` +# Beta(1, 1) = Uniform(0, 1) +if alpha == 1 and beta == 1: + return uniform(0, 1) + +# Beta(0.5, 0.5) - arcsine distribution +if alpha == 0.5 and beta == 0.5: + return sin²(PI * uniform(0, 1) / 2) + +# Beta(a, 1) - power function +if beta == 1: + return pow(uniform(0, 1), 1/alpha) + +# Beta(1, b) - complementary power +if alpha == 1: + return 1 - pow(uniform(0, 1), 1/beta) +``` + +**Algorithm Selection:** + +``` +Function beta_random(alpha, beta): + # Special cases + if alpha == 1 and beta == 1: + return uniform(0, 1) + + if alpha == 1: + return 1 - pow(uniform(0, 1), 1/beta) + + if beta == 1: + return pow(uniform(0, 1), 1/alpha) + + # Johnk for small parameters + if alpha < 1 and beta < 1 and alpha + beta < 1: + return johnk_beta(alpha, beta) + + # Cheng for large parameters + if alpha > 1 and beta > 1: + return cheng_beta(alpha, beta) + + # Gamma ratio (general method) + return beta_random_gamma(alpha, beta) +``` + +### Complete Pseudocode + +```javascript +class BetaDistribution { + constructor(alpha, beta) { + if (alpha <= 0 || beta <= 0) { + throw new Error("Shape parameters must be positive") + } + this.alpha = alpha + this.beta = beta + } + + pdf(x) { + if (x <= 0 || x >= 1) return 0 + + const a = this.alpha + const b = this.beta + + // Use log for numerical stability + const logPdf = (a - 1) * Math.log(x) + + (b - 1) * Math.log(1 - x) + + lgamma(a + b) - lgamma(a) - lgamma(b) + + return Math.exp(logPdf) + } + + cdf(x) { + if (x <= 0) return 0 + if (x >= 1) return 1 + + return regularizedIncompleteBeta(x, this.alpha, this.beta) + } + + quantile(p) { + if (p <= 0) return 0 + if (p >= 1) return 1 + + // Initial guess + let x = this.initialGuess(p) + + // Newton-Raphson + const maxIter = 20 + for (let iter = 0; iter < maxIter; iter++) { + const F = this.cdf(x) + const error = F - p + + if (Math.abs(error) < 1e-14) break + + const f = this.pdf(x) + if (f > 0) { + x = x - error / f + + // Keep in bounds + if (x <= 0) x = 0.001 + if (x >= 1) x = 0.999 + } + } + + return Math.max(0, Math.min(1, x)) + } + + initialGuess(p) { + const a = this.alpha + const b = this.beta + + // Large parameters: normal approximation + if (a > 1 && b > 1) { + const mu = a / (a + b) + const variance = (a * b) / ((a + b) * (a + b) * (a + b + 1)) + const z = normal_quantile(p) + let x = mu + z * Math.sqrt(variance) + return Math.max(0.01, Math.min(0.99, x)) + } + + // Use mean + return a / (a + b) + } + + random() { + const a = this.alpha + const b = this.beta + + // Special cases + if (a === 1 && b === 1) { + return Math.random() + } + + if (a === 1) { + return 1 - Math.pow(Math.random(), 1/b) + } + + if (b === 1) { + return Math.pow(Math.random(), 1/a) + } + + // Johnk for small parameters + if (a < 1 && b < 1 && a + b < 1) { + while (true) { + const U = Math.random() + const V = Math.random() + const X = Math.pow(U, 1/a) + const Y = Math.pow(V, 1/b) + + if (X + Y <= 1) { + return X / (X + Y) + } + } + } + + // Cheng for large parameters + if (a > 1 && b > 1) { + return this.chengBeta() + } + + // Gamma ratio (general) + const X = gamma_random(a, 1) + const Y = gamma_random(b, 1) + return X / (X + Y) + } + + chengBeta() { + const alpha = this.alpha + const beta = this.beta + + const a = Math.min(alpha, beta) + const b = Math.max(alpha, beta) + const A = a + b + const B = Math.sqrt((A - 2) / (2*a*b - A)) + const C = a + 1/B + + while (true) { + const U1 = Math.random() + const U2 = Math.random() + + const V = B * Math.log(U1 / (1 - U1)) + const W = a * Math.exp(V) + + const z = U1 * U1 * U2 + const r = C * V - Math.log(4) + const s = a + r - W + + if (s + 1 + Math.log(5) >= 5*z) { + const X = W / (b + W) + return alpha === a ? X : 1 - X + } + + if (s >= Math.log(z)) { + const X = W / (b + W) + return alpha === a ? X : 1 - X + } + } + } +} +``` + +**Mean and Variance:** +``` +Mean: μ = α / (α + β) +Variance: σ² = αβ / [(α + β)² (α + β + 1)] +Mode (for α, β > 1): (α - 1) / (α + β - 2) +``` + +--- + +## Task 3.6: Exponential Distribution + +### Mathematical Foundation + +The exponential distribution is the simplest continuous distribution with "memoryless" property. + +**Probability Density Function:** +``` +f(x; λ) = λ * exp(-λx) for x ≥ 0 +``` + +Where λ > 0 is the rate parameter. + +**Alternative parameterization (scale parameter β = 1/λ):** +``` +f(x; β) = (1/β) * exp(-x/β) for x ≥ 0 +``` + +**Cumulative Distribution Function:** +``` +F(x; λ) = 1 - exp(-λx) for x ≥ 0 + = 0 for x < 0 +``` + +**Survival function:** +``` +S(x; λ) = 1 - F(x; λ) = exp(-λx) +``` + +### Inverse CDF (Quantile Function) + +The exponential distribution has a closed-form inverse: + +``` +F⁻¹(p; λ) = -(1/λ) * ln(1 - p) for 0 < p < 1 +``` + +**Algorithm:** +``` +Algorithm exponential_quantile(p, lambda): + if p <= 0: + return 0 + + if p >= 1: + return Infinity + + return -ln(1 - p) / lambda +``` + +**Alternative formulation (numerically equivalent):** +``` +# Since 1-U ~ U for U ~ Uniform(0,1): +F⁻¹(p; λ) = -ln(p) / λ +``` + +This is actually preferred for random number generation as it avoids the subtraction. + +### Random Sampling + +**Method 1: Inverse Transform (optimal)** + +Direct application of the inverse CDF: + +``` +Algorithm exponential_random(lambda): + U = uniform(0, 1) + return -ln(U) / lambda +``` + +**Why this is optimal:** +- Exact transformation +- No rejection +- Very fast (one logarithm) +- Numerically stable + +**Method 2: Using (1-U) for numerical reasons** + +``` +Algorithm exponential_random_alt(lambda): + U = uniform(0, 1) + return -ln(1 - U) / lambda +``` + +Both methods are equivalent since U and 1-U have the same distribution. + +**Method 3: Ziggurat (for high-performance applications)** + +Similar to normal ziggurat, optimized for exponential: + +``` +Constants: + r = 7.69711747013104972 // Right tail start + v = 0.003949659822581572 // Area + n = 256 // Number of layers + +Algorithm ziggurat_exponential(lambda): + loop: + j = random_uint32() + i = j & 0xFF # Low 8 bits for layer + + x = j * x[i] # Candidate value + + if j < k[i]: # Fast acceptance + return x / lambda + + if i == 0: # Tail region + return (r + exponential_random_slow(1)) / lambda + + # Slow acceptance test + if y[i+1] + uniform() * (y[i] - y[i+1]) < exp(-x): + return x / lambda +``` + +### Properties and Special Cases + +**Memoryless Property:** +``` +P(X > s + t | X > s) = P(X > t) + +In other words: +exp(-λ(s+t)) / exp(-λs) = exp(-λt) +``` + +**Relation to Other Distributions:** + +1. **Gamma**: Exponential(λ) = Gamma(1, 1/λ) +2. **Weibull**: Exponential(λ) = Weibull(1, 1/λ) +3. **Chi-square**: Exponential(1/2) = χ²(2) +4. **Minimum of exponentials**: + ``` + If X₁, ..., Xₙ ~ Exp(λᵢ) independent, then + min(X₁, ..., Xₙ) ~ Exp(Σᵢ λᵢ) + ``` + +### Complete Pseudocode + +```javascript +class ExponentialDistribution { + constructor(rate = 1) { + if (rate <= 0) { + throw new Error("Rate parameter must be positive") + } + this.lambda = rate + } + + pdf(x) { + if (x < 0) return 0 + return this.lambda * Math.exp(-this.lambda * x) + } + + cdf(x) { + if (x < 0) return 0 + return 1 - Math.exp(-this.lambda * x) + } + + quantile(p) { + if (p <= 0) return 0 + if (p >= 1) return Infinity + + // Exact closed form + return -Math.log(1 - p) / this.lambda + } + + random() { + // Inverse transform method + // Use -log(U) instead of -log(1-U) to save one subtraction + return -Math.log(Math.random()) / this.lambda + } + + // Statistics + mean() { + return 1 / this.lambda + } + + variance() { + return 1 / (this.lambda * this.lambda) + } + + standardDeviation() { + return 1 / this.lambda + } + + // Survival function (1 - CDF) + survival(x) { + if (x < 0) return 1 + return Math.exp(-this.lambda * x) + } + + // Hazard function (constant for exponential) + hazard(x) { + if (x < 0) return 0 + return this.lambda + } +} +``` + +**Alternative Parameterization (scale β = 1/λ):** + +```javascript +class ExponentialDistributionScale { + constructor(scale = 1) { + if (scale <= 0) { + throw new Error("Scale parameter must be positive") + } + this.beta = scale + } + + pdf(x) { + if (x < 0) return 0 + return (1 / this.beta) * Math.exp(-x / this.beta) + } + + cdf(x) { + if (x < 0) return 0 + return 1 - Math.exp(-x / this.beta) + } + + quantile(p) { + if (p <= 0) return 0 + if (p >= 1) return Infinity + + return -this.beta * Math.log(1 - p) + } + + random() { + return -this.beta * Math.log(Math.random()) + } + + mean() { + return this.beta + } + + variance() { + return this.beta * this.beta + } +} +``` + +**Numerical Notes:** + +``` +1. For very small λ (large mean): + - exp(-λx) may underflow for large x + - Use log-space computations if needed + +2. For very large λ (small mean): + - PDF may be huge at x=0 + - Ensure proper normalization + +3. For p very close to 1: + - ln(1-p) loses precision + - Use ln1p(-p) if available: -ln1p(-p) / lambda + +4. For p very close to 0: + - -ln(p) is well-behaved + - No special handling needed +``` + +--- + +## Task 3.7: Gamma Distribution + +### Mathematical Foundation + +**Probability Density Function:** +``` +f(x; α, β) = (1 / (Γ(α) * β^α)) * x^(α-1) * exp(-x/β) +``` + +For x > 0, where: +- α > 0 (shape parameter) +- β > 0 (scale parameter) +- Γ(α) is the gamma function + +**Alternative parameterization (rate θ = 1/β):** +``` +f(x; α, θ) = (θ^α / Γ(α)) * x^(α-1) * exp(-θx) +``` + +**Log PDF (numerically stable):** +``` +ln f(x; α, β) = (α-1)*ln(x) - x/β - α*ln(β) - ln(Γ(α)) +``` + +**Cumulative Distribution Function:** +``` +F(x; α, β) = γ(α, x/β) / Γ(α) = P(α, x/β) +``` + +Where: +- γ(α, x) = lower incomplete gamma function +- P(α, x) = regularized lower incomplete gamma function + +### Inverse CDF (Quantile Function) + +**Newton-Raphson with Wilson-Hilferty Initial Guess:** + +``` +Algorithm gamma_quantile(p, alpha, beta): + if p <= 0: return 0 + if p >= 1: return Infinity + + # Special cases + if alpha == 1: + # Exponential distribution + return -beta * ln(1 - p) + + # Wilson-Hilferty approximation for initial guess + x0 = wilson_hilferty_guess(p, alpha, beta) + + x = x0 + max_iter = 20 + + for iter in 1..max_iter: + # Compute CDF using incomplete gamma + F_x = regularized_gamma_P(alpha, x/beta) + + error = F_x - p + + if abs(error) < 1e-14: + break + + # PDF (derivative) + f_x = pow(x, alpha-1) * exp(-x/beta) / (gamma(alpha) * pow(beta, alpha)) + + if f_x > 0: + dx = -error / f_x + x = x + dx + + if x <= 0: + x = x0 / 2 + + return max(0, x) + +Function wilson_hilferty_guess(p, alpha, beta): + # For alpha > 0, use Wilson-Hilferty transformation + # Gamma(alpha, beta) ≈ beta * alpha * (1 - 1/(9*alpha) + z/(3*sqrt(alpha)))³ + + if alpha < 0.1: + # For very small alpha, use power approximation + return beta * pow(-ln(1 - p), 1/alpha) + + z = normal_quantile(p) + h = 1 / (9 * alpha) + x = alpha * pow(1 - h + z * sqrt(h), 3) + + return beta * x + +Function best_guess(p, alpha, beta): + if alpha >= 1: + return wilson_hilferty_guess(p, alpha, beta) + else: + # For alpha < 1, use improved approximation + if p < 0.01: + return beta * pow(p * gamma(alpha+1), 1/alpha) + else: + return wilson_hilferty_guess(p, alpha, beta) +``` + +### Random Sampling + +**Algorithm Selection by Shape Parameter:** + +``` +Function gamma_random(alpha, beta): + if alpha < 1: + return gamma_small_shape(alpha, beta) + elif alpha == 1: + return exponential_random(beta) + else: # alpha > 1 + return marsaglia_tsang(alpha, beta) +``` + +**Method 1: Marsaglia-Tsang (2000) - For α > 1** + +Most efficient general-purpose algorithm: + +``` +Algorithm marsaglia_tsang(alpha, beta): + # Preprocessing + d = alpha - 1/3 + c = 1 / sqrt(9 * d) + + loop: + # Generate Z ~ N(0,1) and U ~ Uniform(0,1) + Z = standard_normal() + U = uniform(0, 1) + + V = (1 + c*Z)³ + + # Quick rejection + if V <= 0: + continue + + # Acceptance test + if ln(U) < 0.5*Z² + d - d*V + d*ln(V): + return beta * d * V +``` + +**Detailed Marsaglia-Tsang with optimizations:** + +``` +Algorithm marsaglia_tsang_optimized(alpha, beta): + d = alpha - 1/3 + c = 1 / sqrt(9 * d) + c2 = c * c + + loop: + # Generate proposals + loop: + Z = standard_normal() + V = 1 + c * Z + if V > 0: + break + + V = V * V * V + U = uniform(0, 1) + Z2 = Z * Z + + # Fast acceptance (squeeze) + if U < 1 - 0.0331 * Z2 * Z2: + return beta * d * V + + # Log acceptance test + if ln(U) < 0.5*Z2 + d*(1 - V + ln(V)): + return beta * d * V +``` + +**Method 2: Ahrens-Dieter (1974) - For α < 1** + +Efficient for small shape parameters: + +``` +Algorithm ahrens_dieter(alpha, beta): + # Only for 0 < alpha < 1 + b = (E + alpha) / E # E = exp(1) ≈ 2.71828 + + loop: + U1 = uniform(0, 1) + P = b * U1 + + if P <= 1: + X = pow(P, 1/alpha) + U2 = uniform(0, 1) + + if U2 <= exp(-X): + return beta * X + else: + X = -ln((b - P) / alpha) + U2 = uniform(0, 1) + + if U2 <= pow(X, alpha - 1): + return beta * X +``` + +**Method 3: Johnk (1964) - For α + α' < 1** + +When generating Gamma(α) and need Gamma(α'): + +``` +Algorithm johnk_gamma(alpha, beta): + # Works when alpha < 1 + loop: + U = uniform(0, 1) + V = uniform(0, 1) + + X = pow(U, 1/alpha) + Y = pow(V, 1/(1-alpha)) + + if X + Y <= 1: + E = exponential_random(1) + return beta * X * E / (X + Y) +``` + +**Method 4: For Integer α (Erlang distribution)** + +Sum of exponentials: + +``` +Algorithm gamma_integer_shape(k, beta): + # For alpha = k (integer) + # Gamma(k, beta) = sum of k independent Exp(1/beta) + + product = 1.0 + for i in 1..k: + product *= uniform(0, 1) + + return -beta * ln(product) +``` + +### Special Cases and Optimizations + +**α = 1: Exponential** +``` +Gamma(1, β) = Exponential(β) +X = -β * ln(U) +``` + +**α = k/2 (integer k): Chi-Square** +``` +Gamma(k/2, 2) = χ²(k) +``` + +**α = n (integer): Erlang** +``` +Gamma(n, β) = Erlang(n, β) = sum of n Exp(β) +X = -β * ln(∏ᵢ Uᵢ) +``` + +### Complete Pseudocode + +```javascript +class GammaDistribution { + constructor(shape, scale = 1) { + if (shape <= 0 || scale <= 0) { + throw new Error("Shape and scale must be positive") + } + this.alpha = shape + this.beta = scale + } + + pdf(x) { + if (x <= 0) return 0 + + const a = this.alpha + const b = this.beta + + // Log-space for numerical stability + const logPdf = (a - 1) * Math.log(x) - x/b - + a * Math.log(b) - lgamma(a) + + return Math.exp(logPdf) + } + + cdf(x) { + if (x <= 0) return 0 + + // P(alpha, x/beta) + return regularizedGammaP(this.alpha, x / this.beta) + } + + quantile(p) { + if (p <= 0) return 0 + if (p >= 1) return Infinity + + const a = this.alpha + const b = this.beta + + // Special case: Exponential + if (a === 1) { + return -b * Math.log(1 - p) + } + + // Initial guess + let x = this.wilsonHilfertyGuess(p) + + // Newton-Raphson + for (let iter = 0; iter < 20; iter++) { + const F = this.cdf(x) + const error = F - p + + if (Math.abs(error) < 1e-14) break + + const f = this.pdf(x) + if (f > 0) { + x = x - error / f + if (x <= 0) x = 0.001 + } + } + + return Math.max(0, x) + } + + wilsonHilfertyGuess(p) { + const a = this.alpha + const b = this.beta + + if (a < 0.1) { + return b * Math.pow(-Math.log(1 - p), 1/a) + } + + const z = normal_quantile(p) + const h = 1 / (9 * a) + const x = a * Math.pow(1 - h + z * Math.sqrt(h), 3) + + return b * Math.max(0.001, x) + } + + random() { + const a = this.alpha + const b = this.beta + + // Special cases + if (a === 1) { + return -b * Math.log(Math.random()) + } + + if (Number.isInteger(a) && a <= 20) { + // Erlang: sum of exponentials + let product = 1 + for (let i = 0; i < a; i++) { + product *= Math.random() + } + return -b * Math.log(product) + } + + if (a < 1) { + return this.ahrensDieter(a, b) + } + + // General case: Marsaglia-Tsang + return this.marsagliaTsang(a, b) + } + + marsagliaTsang(alpha, beta) { + const d = alpha - 1/3 + const c = 1 / Math.sqrt(9 * d) + + while (true) { + let Z, V + + // Generate V = (1 + c*Z)³ > 0 + do { + Z = standard_normal() + V = 1 + c * Z + } while (V <= 0) + + V = V * V * V + const U = Math.random() + const Z2 = Z * Z + + // Fast acceptance + if (U < 1 - 0.0331 * Z2 * Z2) { + return beta * d * V + } + + // Log acceptance + if (Math.log(U) < 0.5*Z2 + d*(1 - V + Math.log(V))) { + return beta * d * V + } + } + } + + ahrensDieter(alpha, beta) { + const E = Math.E + const b = (E + alpha) / E + + while (true) { + const U1 = Math.random() + const P = b * U1 + + if (P <= 1) { + const X = Math.pow(P, 1/alpha) + const U2 = Math.random() + + if (U2 <= Math.exp(-X)) { + return beta * X + } + } else { + const X = -Math.log((b - P) / alpha) + const U2 = Math.random() + + if (U2 <= Math.pow(X, alpha - 1)) { + return beta * X + } + } + } + } + + // Statistics + mean() { + return this.alpha * this.beta + } + + variance() { + return this.alpha * this.beta * this.beta + } + + mode() { + if (this.alpha >= 1) { + return (this.alpha - 1) * this.beta + } + return 0 + } +} +``` + +**Key Performance Notes:** + +``` +1. Marsaglia-Tsang is fastest for α > 1 + - Average: 1.3-1.5 iterations + - No tables needed + - Very efficient + +2. Ahrens-Dieter works well for α < 1 + - Average: ~2 iterations + - Simple implementation + +3. For integer α, sum of exponentials is exact + - Use log of product for numerical stability + +4. For very large α (> 1000): + - Use normal approximation + - Gamma(α, β) ≈ N(αβ, αβ²) +``` + +--- + +## Task 3.8: Incomplete Gamma Function + +The incomplete gamma functions are essential for computing CDFs of gamma, chi-square, and Poisson distributions. + +### Definitions + +**Lower Incomplete Gamma:** +``` +γ(a, x) = ∫₀ˣ t^(a-1) e^(-t) dt +``` + +**Upper Incomplete Gamma:** +``` +Γ(a, x) = ∫ₓ^∞ t^(a-1) e^(-t) dt +``` + +**Relationship:** +``` +γ(a, x) + Γ(a, x) = Γ(a) +``` + +**Regularized Forms:** +``` +P(a, x) = γ(a, x) / Γ(a) # Regularized lower +Q(a, x) = Γ(a, x) / Γ(a) # Regularized upper + +P(a, x) + Q(a, x) = 1 +``` + +### Series Expansion (for x < a + 1) + +**For Lower Incomplete Gamma γ(a, x):** + +``` +γ(a, x) = e^(-x) * x^a * Σ(n=0 to ∞) [Γ(a) / Γ(a + n + 1)] * x^n + = e^(-x) * x^a * [1/a + x/(a+1) + x²/(a+1)(a+2) + ...] +``` + +**Regularized form P(a, x):** +``` +P(a, x) = [e^(-x) * x^a / Γ(a)] * Σ(n=0 to ∞) [x^n / (a)_(n+1)] + +where (a)_n = a(a+1)(a+2)...(a+n-1) is the Pochhammer symbol +``` + +**Algorithm:** + +``` +Algorithm incomplete_gamma_series(a, x): + # Only use for x < a + 1 + if x >= a + 1: + return "use continued fraction" + + if x <= 0: + return 0 + + # Log of initial term: -x + a*ln(x) - ln(Γ(a)) + log_term = -x + a * ln(x) - lgamma(a) + + # Series computation + sum = 1.0 + term = 1.0 + ap = a + + max_iter = 1000 + epsilon = 1e-15 + + for n in 1..max_iter: + ap += 1 + term *= x / ap + sum += term + + if abs(term) < abs(sum) * epsilon: + break + + # Return γ(a,x) + return exp(log_term) * sum + +Algorithm regularized_gamma_P_series(a, x): + # Returns P(a, x) = γ(a, x) / Γ(a) + + if x <= 0: + return 0 + + # log(-x + a*ln(x) - ln(Γ(a))) = log(x^a * e^(-x) / Γ(a)) + log_prefix = -x + a * ln(x) - lgamma(a) + + sum = 1.0 + term = 1.0 + ap = a + + for n in 1..1000: + ap += 1 + term *= x / ap + sum += term + + if abs(term / sum) < 1e-15: + break + + return exp(log_prefix) * sum +``` + +### Continued Fraction (for x > a + 1) + +**For Upper Incomplete Gamma Γ(a, x):** + +Using Lentz's method for continued fraction evaluation: + +``` +Γ(a, x) / Γ(a) = e^(-x) * x^a * 1 / [x + 1-a - 1(1-a)/(x+3-a-) 2(2-a)/(x+5-a-)...] +``` + +**Standard continued fraction form:** +``` +e^(-x) * x^a / [x + (1-a)/1 + 1/x + (2-a)/1 + 2/x + (3-a)/1 + ...] +``` + +**More precisely:** +``` +Q(a, x) = [e^(-x) * x^a] / CF(a, x) + +where CF(a, x) = x + 1-a - (1)(1-a)/(x+3-a-) (2)(2-a)/(x+5-a-) ... +``` + +**Lentz's Algorithm:** + +``` +Algorithm incomplete_gamma_continued_fraction(a, x): + # Only use for x >= a + 1 + if x < a + 1: + return "use series" + + # Log of coefficient: -x + a*ln(x) - ln(Γ(a)) + log_coeff = -x + a * ln(x) - lgamma(a) + + # Lentz's method for continued fraction + # CF = b₀ + a₁/(b₁ + a₂/(b₂ + a₃/(b₃ + ...))) + + # Modified Lentz algorithm + tiny = 1e-30 + + # Initial values for continued fraction + # CF = x + (1-a) - 1*(1-a)/(x+3-a-) 2*(2-a)/(x+5-a-)... + # Rewrite as: b₀ + a₁/b₁ + a₂/b₂ + ... + # where aₙ = -n*(n-a), bₙ = x + 2n + 1 - a + + b = x + 1 - a + c = 1 / tiny + d = 1 / b + h = d + + max_iter = 1000 + epsilon = 1e-15 + + for i in 1..max_iter: + an = -i * (i - a) + b += 2 + + d = an * d + b + if abs(d) < tiny: + d = tiny + + c = b + an / c + if abs(c) < tiny: + c = tiny + + d = 1 / d + delta = c * d + h *= delta + + if abs(delta - 1) < epsilon: + break + + # Return Q(a, x) = Γ(a, x) / Γ(a) + return exp(log_coeff) / h + +Algorithm regularized_gamma_Q_cf(a, x): + # Same as above, returns Q(a, x) + return incomplete_gamma_continued_fraction(a, x) +``` + +### Combined Algorithm (automatic selection) + +``` +Algorithm regularized_incomplete_gamma_P(a, x): + # Returns P(a, x) = γ(a, x) / Γ(a) + + if x < 0 or a <= 0: + throw error + + if x == 0: + return 0 + + if x >= a + 1: + # Use continued fraction for Q, then P = 1 - Q + return 1 - regularized_gamma_Q_cf(a, x) + else: + # Use series for P + return regularized_gamma_P_series(a, x) + +Algorithm regularized_incomplete_gamma_Q(a, x): + # Returns Q(a, x) = Γ(a, x) / Γ(a) + + if x < 0 or a <= 0: + throw error + + if x == 0: + return 1 + + if x >= a + 1: + # Use continued fraction for Q + return regularized_gamma_Q_cf(a, x) + else: + # Use series for P, then Q = 1 - P + return 1 - regularized_gamma_P_series(a, x) +``` + +### Inverse Incomplete Gamma + +For finding x such that P(a, x) = p: + +``` +Algorithm inverse_regularized_gamma_P(a, p): + # Find x such that P(a, x) = p + + if p <= 0: + return 0 + if p >= 1: + return Infinity + + # Initial guess + if a > 1: + # Use Wilson-Hilferty + t = normal_quantile(p) + s = 1 / (9 * a) + x = a * pow(1 - s + t * sqrt(s), 3) + else: + # For small a + t = pow(p * gamma(a + 1), 1/a) + x = t + + # Halley's method (uses second derivative) + for iter in 1..10: + # Current P and error + Px = regularized_incomplete_gamma_P(a, x) + error = Px - p + + if abs(error) < 1e-12: + break + + # First derivative: d/dx P(a,x) = x^(a-1) * e^(-x) / Γ(a) + t = pow(x, a-1) * exp(-x) / gamma(a) + + if t == 0: + break + + # Second derivative + u = (a - 1) / x - 1 + + # Halley update + dx = -error / (t * (1 - 0.5 * error * u / t)) + x = x + dx + + if x <= 0: + x = 0.5 * (x - dx) + + return max(0, x) +``` + +### Complete Pseudocode + +```javascript +class IncompleteGamma { + // Regularized lower incomplete gamma P(a, x) + static P(a, x) { + if (x < 0 || a <= 0) { + throw new Error("Invalid parameters") + } + + if (x === 0) return 0 + if (x === Infinity) return 1 + + // Choose method based on x vs a + if (x < a + 1) { + return this.P_series(a, x) + } else { + return 1 - this.Q_continued_fraction(a, x) + } + } + + // Regularized upper incomplete gamma Q(a, x) + static Q(a, x) { + if (x < 0 || a <= 0) { + throw new Error("Invalid parameters") + } + + if (x === 0) return 1 + if (x === Infinity) return 0 + + if (x >= a + 1) { + return this.Q_continued_fraction(a, x) + } else { + return 1 - this.P_series(a, x) + } + } + + // Series expansion for P + static P_series(a, x) { + const logPrefix = -x + a * Math.log(x) - lgamma(a) + + let sum = 1.0 + let term = 1.0 + let ap = a + + for (let n = 1; n <= 1000; n++) { + ap += 1 + term *= x / ap + sum += term + + if (Math.abs(term / sum) < 1e-15) { + break + } + } + + return Math.exp(logPrefix) * sum + } + + // Continued fraction for Q (Lentz's method) + static Q_continued_fraction(a, x) { + const logCoeff = -x + a * Math.log(x) - lgamma(a) + const tiny = 1e-30 + + let b = x + 1 - a + let c = 1 / tiny + let d = 1 / b + let h = d + + for (let i = 1; i <= 1000; i++) { + const an = -i * (i - a) + b += 2 + + d = an * d + b + if (Math.abs(d) < tiny) d = tiny + + c = b + an / c + if (Math.abs(c) < tiny) c = tiny + + d = 1 / d + const delta = c * d + h *= delta + + if (Math.abs(delta - 1) < 1e-15) { + break + } + } + + return Math.exp(logCoeff) / h + } + + // Inverse: find x such that P(a, x) = p + static inverseP(a, p) { + if (p <= 0) return 0 + if (p >= 1) return Infinity + + // Initial guess + let x + if (a > 1) { + const t = normal_quantile(p) + const s = 1 / (9 * a) + x = a * Math.pow(1 - s + t * Math.sqrt(s), 3) + } else { + x = Math.pow(p * gamma(a + 1), 1/a) + } + + if (x <= 0) x = 0.001 + + // Halley's method + for (let iter = 0; iter < 10; iter++) { + const Px = this.P(a, x) + const error = Px - p + + if (Math.abs(error) < 1e-12) break + + const t = Math.pow(x, a-1) * Math.exp(-x) / gamma(a) + if (t === 0) break + + const u = (a - 1) / x - 1 + const dx = -error / (t * (1 - 0.5 * error * u / t)) + + x = x + dx + if (x <= 0) x = 0.5 * (x - dx) + } + + return Math.max(0, x) + } +} +``` + +**Selection Criteria Summary:** + +``` +For P(a, x): + if x < a + 1: + Use series expansion (converges quickly) + else: + Compute Q(a, x) via continued fraction, return 1 - Q + +For Q(a, x): + if x >= a + 1: + Use continued fraction (converges quickly) + else: + Compute P(a, x) via series, return 1 - P + +Why this works: + - Series converges fast when x < a + 1 + - Continued fraction converges fast when x >= a + 1 + - Using the complementary form avoids catastrophic cancellation +``` + +--- + +## Task 3.9: Weibull Distribution + +### Mathematical Foundation + +**Probability Density Function:** +``` +f(x; λ, k) = (k/λ) * (x/λ)^(k-1) * exp(-(x/λ)^k) +``` + +For x ≥ 0, where: +- λ > 0 (scale parameter) +- k > 0 (shape parameter, also called Weibull modulus) + +**Cumulative Distribution Function:** +``` +F(x; λ, k) = 1 - exp(-(x/λ)^k) +``` + +For x ≥ 0. + +**Survival Function:** +``` +S(x; λ, k) = exp(-(x/λ)^k) +``` + +### Inverse CDF (Quantile Function) + +The Weibull distribution has a **closed-form inverse**: + +``` +F⁻¹(p; λ, k) = λ * (-ln(1 - p))^(1/k) +``` + +**Algorithm:** +``` +Algorithm weibull_quantile(p, lambda, k): + if p <= 0: + return 0 + + if p >= 1: + return Infinity + + return lambda * pow(-ln(1 - p), 1/k) +``` + +**Alternative formulation (numerically equivalent):** +``` +# Using -ln(U) ~ Exp(1) for U ~ Uniform(0,1) +F⁻¹(p; λ, k) = λ * (-ln(p))^(1/k) +``` + +This avoids the subtraction and is preferred. + +### Random Sampling + +**Method 1: Inverse Transform (optimal)** + +Direct application of inverse CDF: + +``` +Algorithm weibull_random(lambda, k): + U = uniform(0, 1) + return lambda * pow(-ln(U), 1/k) +``` + +**Why this is optimal:** +- Exact transformation +- No rejection +- Single logarithm and power operation +- Numerically stable + +**Method 2: Alternative (using 1-U)** + +``` +Algorithm weibull_random_alt(lambda, k): + U = uniform(0, 1) + return lambda * pow(-ln(1 - U), 1/k) +``` + +Both are equivalent since U and 1-U have the same distribution. + +### Special Cases + +**k = 1: Exponential Distribution** +``` +Weibull(λ, 1) = Exponential(1/λ) + +f(x; λ, 1) = (1/λ) * exp(-x/λ) +X = λ * (-ln(U)) = -λ * ln(U) +``` + +**k = 2: Rayleigh Distribution** +``` +Weibull(λ, 2) = Rayleigh(λ/√2) + +f(x; λ, 2) = (2x/λ²) * exp(-(x/λ)²) +X = λ * sqrt(-ln(U)) +``` + +**k = 3.5: Approximates Normal** +``` +For k ≈ 3.5, Weibull approximates normal distribution +``` + +### Properties + +**Hazard Function (failure rate):** +``` +h(x; λ, k) = (k/λ) * (x/λ)^(k-1) +``` + +Behavior: +- k < 1: Decreasing failure rate +- k = 1: Constant failure rate (exponential) +- k > 1: Increasing failure rate + +**Mean:** +``` +μ = λ * Γ(1 + 1/k) +``` + +**Variance:** +``` +σ² = λ² * [Γ(1 + 2/k) - Γ²(1 + 1/k)] +``` + +**Mode:** +``` +For k > 1: + mode = λ * ((k-1)/k)^(1/k) + +For k ≤ 1: + mode = 0 +``` + +**Median:** +``` +median = λ * (ln 2)^(1/k) +``` + +### Complete Pseudocode + +```javascript +class WeibullDistribution { + constructor(scale, shape) { + if (scale <= 0 || shape <= 0) { + throw new Error("Scale and shape must be positive") + } + this.lambda = scale + this.k = shape + } + + pdf(x) { + if (x < 0) return 0 + if (x === 0 && this.k < 1) return Infinity + if (x === 0 && this.k === 1) return 1 / this.lambda + if (x === 0) return 0 + + const z = x / this.lambda + + // For numerical stability, use log-space + const logPdf = Math.log(this.k) - Math.log(this.lambda) + + (this.k - 1) * Math.log(z) - + Math.pow(z, this.k) + + return Math.exp(logPdf) + } + + cdf(x) { + if (x < 0) return 0 + + const z = x / this.lambda + return 1 - Math.exp(-Math.pow(z, this.k)) + } + + quantile(p) { + if (p <= 0) return 0 + if (p >= 1) return Infinity + + // Closed form inverse + return this.lambda * Math.pow(-Math.log(1 - p), 1 / this.k) + } + + random() { + // Inverse transform method (optimal for Weibull) + const U = Math.random() + return this.lambda * Math.pow(-Math.log(U), 1 / this.k) + } + + // Survival function + survival(x) { + if (x < 0) return 1 + + const z = x / this.lambda + return Math.exp(-Math.pow(z, this.k)) + } + + // Hazard function + hazard(x) { + if (x < 0) return 0 + if (x === 0) { + if (this.k < 1) return Infinity + if (this.k === 1) return 1 / this.lambda + return 0 + } + + const z = x / this.lambda + return (this.k / this.lambda) * Math.pow(z, this.k - 1) + } + + // Statistics + mean() { + return this.lambda * gamma(1 + 1/this.k) + } + + variance() { + const g1 = gamma(1 + 1/this.k) + const g2 = gamma(1 + 2/this.k) + return this.lambda * this.lambda * (g2 - g1 * g1) + } + + standardDeviation() { + return Math.sqrt(this.variance()) + } + + median() { + return this.lambda * Math.pow(Math.log(2), 1/this.k) + } + + mode() { + if (this.k <= 1) { + return 0 + } + return this.lambda * Math.pow((this.k - 1) / this.k, 1/this.k) + } +} +``` + +**Numerical Considerations:** + +``` +1. For very small x and k > 1: + - PDF → 0 + - Use log-space to avoid underflow + +2. For very large x: + - CDF → 1 + - exp(-(x/λ)^k) underflows to 0 + - Survival function loses precision + +3. For k very large: + - Distribution becomes very peaked + - pow() operation may overflow + - Use log-space computations + +4. For k very small (< 0.1): + - Distribution heavily skewed + - Use high-precision arithmetic if available + +5. For numerical stability in CDF: + - For small p: use CDF directly + - For large p: use survival function +``` + +**Relationship to Other Distributions:** + +``` +1. Exponential: Weibull(λ, 1) = Exp(1/λ) + +2. Rayleigh: Weibull(λ, 2) = Rayleigh(λ/√2) + +3. Maximum of Gumbels: + min(X₁, ..., Xₙ) for Xᵢ ~ Weibull → Gumbel + +4. Power transform of exponential: + If E ~ Exp(1), then (λE)^(1/k) ~ Weibull(λ, k) +``` + +--- + +## Task 3.10: Lognormal Distribution + +### Mathematical Foundation + +The lognormal distribution arises when ln(X) ~ Normal(μ, σ²). + +**Probability Density Function:** +``` +f(x; μ, σ) = (1 / (x σ √(2π))) * exp(-(ln(x) - μ)² / (2σ²)) +``` + +For x > 0, where: +- μ = mean of ln(X) (location parameter, -∞ < μ < ∞) +- σ = standard deviation of ln(X) (scale parameter, σ > 0) + +**Log PDF (numerically stable):** +``` +ln f(x; μ, σ) = -ln(x) - ln(σ) - ½ln(2π) - (ln(x) - μ)² / (2σ²) +``` + +**Cumulative Distribution Function:** + +Since ln(X) ~ N(μ, σ²): + +``` +F(x; μ, σ) = Φ((ln(x) - μ) / σ) + = ½ * [1 + erf((ln(x) - μ) / (σ√2))] +``` + +Where Φ is the standard normal CDF. + +### Inverse CDF (Quantile Function) + +**Closed-form using normal quantile:** + +``` +F⁻¹(p; μ, σ) = exp(μ + σ * Φ⁻¹(p)) +``` + +Where Φ⁻¹ is the standard normal quantile function. + +**Algorithm:** +``` +Algorithm lognormal_quantile(p, mu, sigma): + if p <= 0: + return 0 + + if p >= 1: + return Infinity + + # Get standard normal quantile + z = normal_quantile(p) + + # Transform to lognormal + return exp(mu + sigma * z) +``` + +This is **exact** - no iteration needed! + +### Random Sampling + +**Method 1: Exponential Transform of Normal (optimal)** + +Direct transformation: + +``` +Algorithm lognormal_random(mu, sigma): + Z = standard_normal() + return exp(mu + sigma * Z) +``` + +**Why this is optimal:** +- Exact transformation +- No rejection +- Single normal sample + exp +- Numerically stable + +**Method 2: Using general normal** + +``` +Algorithm lognormal_random_alt(mu, sigma): + X = normal_random(mu, sigma) + return exp(X) +``` + +**Method 3: Using Box-Muller for two samples** + +Generate two lognormals simultaneously: + +``` +Algorithm lognormal_random_pair(mu, sigma): + U1 = uniform(0, 1) + U2 = uniform(0, 1) + + R = sqrt(-2 * ln(U1)) + theta = 2 * PI * U2 + + Z1 = R * cos(theta) + Z2 = R * sin(theta) + + X1 = exp(mu + sigma * Z1) + X2 = exp(mu + sigma * Z2) + + return (X1, X2) +``` + +### Statistics and Properties + +**Mean of Lognormal(μ, σ²):** +``` +E[X] = exp(μ + σ²/2) +``` + +**Variance:** +``` +Var(X) = [exp(σ²) - 1] * exp(2μ + σ²) + = exp(2μ + σ²) * [exp(σ²) - 1] +``` + +**Median:** +``` +median = exp(μ) +``` + +**Mode:** +``` +mode = exp(μ - σ²) +``` + +**Geometric Mean:** +``` +GM = exp(μ) +``` + +**Standard Deviation:** +``` +SD = sqrt(Var(X)) = exp(μ + σ²/2) * sqrt(exp(σ²) - 1) +``` + +**Coefficient of Variation:** +``` +CV = sqrt(exp(σ²) - 1) +``` + +### Parameter Estimation from Moments + +If we know E[X] = m and Var(X) = v: + +``` +σ² = ln(1 + v/m²) +μ = ln(m) - σ²/2 +``` + +**Algorithm:** +``` +Algorithm fit_lognormal_from_moments(mean, variance): + m = mean + v = variance + + sigma_sq = ln(1 + v / (m * m)) + mu = ln(m) - sigma_sq / 2 + + return (mu, sqrt(sigma_sq)) +``` + +### Complete Pseudocode + +```javascript +class LognormalDistribution { + constructor(mu, sigma) { + if (sigma <= 0) { + throw new Error("Sigma must be positive") + } + this.mu = mu + this.sigma = sigma + this.normalDist = new NormalDistribution(mu, sigma) + } + + pdf(x) { + if (x <= 0) return 0 + + const lnx = Math.log(x) + const z = (lnx - this.mu) / this.sigma + + // For numerical stability + const logPdf = -lnx - Math.log(this.sigma) - + 0.5 * Math.log(2 * Math.PI) - + 0.5 * z * z + + return Math.exp(logPdf) + } + + cdf(x) { + if (x <= 0) return 0 + + const z = (Math.log(x) - this.mu) / this.sigma + + // Use standard normal CDF + return 0.5 * (1 + erf(z / Math.SQRT2)) + } + + quantile(p) { + if (p <= 0) return 0 + if (p >= 1) return Infinity + + // Exact: exp(μ + σ * Φ⁻¹(p)) + const z = normal_quantile(p) + return Math.exp(this.mu + this.sigma * z) + } + + random() { + // Exact transformation + const Z = standard_normal() + return Math.exp(this.mu + this.sigma * Z) + } + + // Statistics + mean() { + return Math.exp(this.mu + this.sigma * this.sigma / 2) + } + + variance() { + const s2 = this.sigma * this.sigma + return (Math.exp(s2) - 1) * Math.exp(2*this.mu + s2) + } + + standardDeviation() { + return Math.sqrt(this.variance()) + } + + median() { + return Math.exp(this.mu) + } + + mode() { + return Math.exp(this.mu - this.sigma * this.sigma) + } + + geometricMean() { + return Math.exp(this.mu) + } + + coefficientOfVariation() { + const s2 = this.sigma * this.sigma + return Math.sqrt(Math.exp(s2) - 1) + } + + // Fit from sample moments + static fromMoments(mean, variance) { + const m = mean + const v = variance + + const sigma_sq = Math.log(1 + v / (m * m)) + const mu = Math.log(m) - sigma_sq / 2 + + return new LognormalDistribution(mu, Math.sqrt(sigma_sq)) + } + + // Fit from sample data + static fromData(data) { + // Take log of all values + const logData = data.map(x => Math.log(x)) + + // Compute mean and variance of log data + const n = logData.length + const mu = logData.reduce((a, b) => a + b) / n + + const variance = logData.reduce((sum, x) => + sum + (x - mu) * (x - mu), 0) / (n - 1) + + const sigma = Math.sqrt(variance) + + return new LognormalDistribution(mu, sigma) + } +} +``` + +### Multiplicative Central Limit Theorem + +The lognormal arises naturally: + +``` +If X = X₁ * X₂ * ... * Xₙ where Xᵢ are positive i.i.d., +then ln(X) = ln(X₁) + ln(X₂) + ... + ln(Xₙ) + +By CLT: ln(X) → Normal +Therefore: X → Lognormal +``` + +### Numerical Considerations + +``` +1. For very small x: + - ln(x) → -∞ + - PDF → 0 + - Use log-space to avoid underflow + +2. For very large x: + - ln(x) is large + - CDF → 1 + - Use complementary normal CDF for precision + +3. For σ very large: + - Distribution very spread out + - Mean >> median + - Heavy right tail + +4. For σ very small: + - Distribution concentrated near exp(μ) + - Approaches delta function at exp(μ) + +5. Numerical stability for CDF: + For x >> exp(μ): + Use: 1 - Φ(-(ln(x) - μ)/σ) + Instead of: Φ((ln(x) - μ)/σ) +``` + +### Applications + +``` +1. Finance: Stock prices, returns +2. Reliability: Time to failure +3. Biology: Cell sizes, reaction times +4. Environmental: Pollutant concentrations +5. Economics: Income distributions +``` + +### Relationship to Normal + +``` +If X ~ Lognormal(μ, σ²), then: + ln(X) ~ Normal(μ, σ²) + +If Y ~ Normal(μ, σ²), then: + exp(Y) ~ Lognormal(μ, σ²) + +This makes all computations reducible to normal distribution! +``` + +--- + +## Implementation Strategy for Math.js + +### File Organization + +``` +src/function/probability/ +├── distributions/ +│ ├── normal.js # Task 3.1 +│ ├── studentt.js # Task 3.2 +│ ├── chisquare.js # Task 3.3 +│ ├── fdist.js # Task 3.4 +│ ├── beta.js # Task 3.5 +│ ├── exponential.js # Task 3.6 +│ ├── gamma.js # Task 3.7 +│ ├── weibull.js # Task 3.8 +│ └── lognormal.js # Task 3.9 +└── special/ + └── incompleteGamma.js # Task 3.10 (helper) +``` + +### Shared Dependencies + +All distributions need: + +```javascript +// Common special functions +import { erf, erfc } from '../special/erf.js' +import { gamma, lgamma } from '../special/gamma.js' +import { incompleteBeta } from '../special/incompleteBeta.js' +import { incompleteGamma } from '../special/incompleteGamma.js' + +// Normal distribution (used by many) +import { normalPdf, normalCdf, normalQuantile } from './distributions/normal.js' + +// Random number generation +import { randomUniform } from '../../random/randomUniform.js' +import { randomNormal } from '../../random/randomNormal.js' +``` + +### Testing Requirements + +For each distribution, test: + +```javascript +describe('Distribution Tests', () => { + // 1. PDF properties + test('PDF integrates to 1') + test('PDF non-negative') + test('PDF matches known values') + + // 2. CDF properties + test('CDF monotonic increasing') + test('CDF(−∞) = 0, CDF(∞) = 1') + test('CDF matches known values') + + // 3. Quantile properties + test('Quantile inverts CDF') + test('Quantile(0.5) = median') + test('Quantile matches tables') + + // 4. Random sampling + test('Sample mean converges') + test('Sample variance converges') + test('K-S test for distribution') + + // 5. Edge cases + test('Extreme parameter values') + test('Numerical stability') + test('Special cases') +}) +``` + +### Performance Benchmarks + +```javascript +// Benchmark targets (WASM vs JS) +const benchmarks = { + normal: { + pdf: { js: '10M/s', wasm: '50M/s' }, + cdf: { js: '5M/s', wasm: '25M/s' }, + quantile: { js: '2M/s', wasm: '10M/s' }, + random: { js: '20M/s', wasm: '100M/s' } + }, + // ... similar for other distributions +} +``` + +### WASM Implementation Priority + +1. **High priority** (hot path, heavy compute): + - Normal: Ziggurat random, erf-based CDF + - Gamma: Marsaglia-Tsang, incomplete gamma + - Beta: Cheng's algorithm, incomplete beta + - Incomplete gamma/beta functions + +2. **Medium priority**: + - Student's t, Chi-square, F distribution + - Numerical inversions (Newton-Raphson) + +3. **Low priority** (already fast): + - Exponential (closed form) + - Weibull (closed form) + - Lognormal (delegates to normal) + +--- + +## References and Further Reading + +1. **Abramowitz & Stegun** (1964). *Handbook of Mathematical Functions* + - Standard reference for approximations + +2. **Press et al.** (2007). *Numerical Recipes* (3rd ed.) + - Practical algorithms and implementations + +3. **Marsaglia & Tsang** (2000). "A Simple Method for Generating Gamma Variables" + - Modern gamma sampling + +4. **Acklam, P.J.** (2010). "An algorithm for computing the inverse normal CDF" + - High-precision normal quantile + +5. **Cheng, R.C.H.** (1978). "Generating Beta Variates with Nonintegral Shape Parameters" + - Efficient beta sampling + +6. **DiDonato & Morris** (1992). "Algorithm 708: Significant Digit Computation of the Incomplete Beta" + - Accurate incomplete beta + +7. **Boost C++ Libraries** - Math Toolkit + - Reference implementation for validation + +8. **NIST Digital Library of Mathematical Functions** + - https://dlmf.nist.gov/ + - Comprehensive mathematical reference + +--- + +## Document Metadata + +- **Version**: 1.0 +- **Date**: 2025-11-29 +- **Status**: Implementation Ready +- **Target**: Math.js Phase 3 Refactoring +- **Dependencies**: Phase 1 (Special Functions), Phase 2 (Matrix Operations) +- **Estimated Effort**: 40-60 hours for all 10 tasks +- **Performance Target**: 3-8x speedup with WASM + +--- + +**END OF DOCUMENT** diff --git a/PHASE_4_PROBABILITY_DISTRIBUTIONS_DISCRETE.md b/PHASE_4_PROBABILITY_DISTRIBUTIONS_DISCRETE.md new file mode 100644 index 0000000000..ab30b0c13e --- /dev/null +++ b/PHASE_4_PROBABILITY_DISTRIBUTIONS_DISCRETE.md @@ -0,0 +1,2830 @@ +# Phase 4: Probability Distributions (Part 2 - Discrete & Additional) + +## Overview + +This phase implements discrete probability distributions and additional continuous distributions to complement Phase 3. These distributions are essential for statistical computing, hypothesis testing, and stochastic modeling. + +**Status**: Not Started +**Dependencies**: Phase 3 (Continuous Distributions), Phase 1 (Special Functions) +**Estimated Complexity**: High +**Performance Target**: 3-8x speedup with WASM + +--- + +## Task 4.1: Poisson Distribution + +### Mathematical Formulas + +**Probability Mass Function (PMF)**: +``` +P(X = k) = (λ^k * e^(-λ)) / k! + +where: +- k = 0, 1, 2, ... (non-negative integer) +- λ > 0 (rate parameter) +``` + +**Cumulative Distribution Function (CDF)**: +``` +F(k) = P(X ≤ k) = Σ(i=0 to k) [(λ^i * e^(-λ)) / i!] + = Γ(⌊k+1⌋, λ) / ⌊k+1⌋! (using regularized incomplete gamma) +``` + +**Inverse CDF** (Quantile Function): +``` +Q(p) = min{k : F(k) ≥ p} +``` + +### Algorithms + +#### PMF Computation + +**Method**: Direct computation with overflow prevention +``` +Algorithm: poissonPMF(k, lambda) +Input: k (integer ≥ 0), lambda (rate > 0) +Output: P(X = k) + +1. If k < 0: return 0 +2. If lambda ≤ 0: error "lambda must be positive" + +3. Special cases: + - If lambda = 0: return (k = 0) ? 1 : 0 + - If k = 0: return exp(-lambda) + +4. Use logarithmic computation to prevent overflow: + log_pmf = k * log(lambda) - lambda - log_gamma(k + 1) + +5. Return exp(log_pmf) + +Numerical stability: +- For lambda > 700: use Normal approximation +- For very large k: use Stirling's approximation for log(k!) +``` + +#### CDF Computation + +**Method**: Optimized summation with early termination +``` +Algorithm: poissonCDF(k, lambda) +Input: k (integer), lambda (rate > 0) +Output: P(X ≤ k) + +1. If k < 0: return 0 +2. If lambda ≤ 0: error "lambda must be positive" + +3. Special cases: + - If lambda = 0: return 1 + - If k = 0: return exp(-lambda) + +4. For large lambda (> 10), use regularized gamma: + return gamma_inc_upper(floor(k + 1), lambda) + +5. Otherwise, use direct summation: + sum = 0 + term = exp(-lambda) // P(X = 0) + + for i = 0 to k: + sum += term + if i < k: + term *= lambda / (i + 1) + + // Early termination + if term < sum * EPSILON: + break + + return sum + +Optimization: +- Start from mode if k > lambda +- Use recurrence: P(k) = P(k-1) * lambda / k +``` + +#### Inverse CDF (Quantile) + +**Method**: Binary search with guided initialization +``` +Algorithm: poissonInverseCDF(p, lambda) +Input: p ∈ [0, 1], lambda (rate > 0) +Output: smallest k such that P(X ≤ k) ≥ p + +1. Validate: + - If p < 0 or p > 1: error + - If p = 0: return 0 + - If p = 1: return Infinity + +2. Initial guess using Normal approximation: + if lambda > 10: + mu = lambda + sigma = sqrt(lambda) + k_init = floor(mu + sigma * norm_inv_cdf(p)) + else: + k_init = floor(lambda) + +3. Refine using binary search: + k_low = 0 + k_high = max(k_init * 2, lambda + 10 * sqrt(lambda)) + + while k_low < k_high: + k_mid = floor((k_low + k_high) / 2) + cdf_mid = poissonCDF(k_mid, lambda) + + if cdf_mid < p: + k_low = k_mid + 1 + else: + k_high = k_mid + +4. Return k_low + +Optimization: +- For lambda < 10: linear search from mode +- Cache CDF values during search +``` + +#### Random Sampling + +**Method 1**: Inverse Transform (small λ) +``` +Algorithm: poissonRandom_InverseTransform(lambda) +Input: lambda ≤ 10 +Output: Random Poisson variate + +1. L = exp(-lambda) +2. k = 0 +3. p = 1 + +4. do: + k = k + 1 + u = uniform(0, 1) + p = p * u + while p > L + +5. return k - 1 +``` + +**Method 2**: Ratio-of-Uniforms (large λ) +``` +Algorithm: poissonRandom_RatioUniforms(lambda) +Input: lambda > 10 +Output: Random Poisson variate + +// PTRS (Poisson-Transformed Rejection Sampling) +1. Setup constants: + smu = sqrt(lambda) + b = 0.931 + 2.53 * smu + a = -0.059 + 0.02483 * b + inv_alpha = 1.1239 + 1.1328 / (b - 3.4) + v_r = 0.9277 - 3.6224 / (b - 2) + +2. Loop until acceptance: + while true: + u = uniform(0, 1) - 0.5 + v = uniform(0, 1) + + us = 0.5 - abs(u) + k = floor((2 * a / us + b) * u + lambda + 0.43) + + // Quick acceptance + if us >= 0.07919 and v <= v_r: + return k + + if k < 0 or (us < 0.013 and v > us): + continue + + // Logarithmic acceptance + log_ratio = (k + 0.5) * log(lambda / k) - lambda - k + log(k) + - log(2 * PI * k) / 2 + + if log(v * inv_alpha / (a / (us * us) + b)) <= log_ratio: + return k +``` + +### Complete Pseudocode + +```typescript +class PoissonDistribution { + constructor(lambda: number) { + if (lambda <= 0) throw Error("lambda must be positive") + this.lambda = lambda + this.useApproximation = lambda > 10 + } + + pmf(k: integer): number { + if (k < 0) return 0 + + if (this.lambda > 700) { + // Use Normal approximation for extreme lambda + return normalPDF(k, this.lambda, sqrt(this.lambda)) + } + + // Logarithmic computation + const logPMF = k * log(this.lambda) - this.lambda - logGamma(k + 1) + return exp(logPMF) + } + + cdf(k: integer): number { + if (k < 0) return 0 + + if (this.useApproximation) { + // Use regularized upper incomplete gamma + return gammaIncUpper(floor(k + 1), this.lambda) + } + + // Direct summation with recurrence + let sum = 0 + let term = exp(-this.lambda) + + for (let i = 0; i <= k; i++) { + sum += term + if (i < k) { + term *= this.lambda / (i + 1) + } + if (term < sum * Number.EPSILON) break + } + + return min(sum, 1.0) + } + + inverseCDF(p: number): integer { + if (p < 0 || p > 1) throw Error("p must be in [0,1]") + if (p === 0) return 0 + if (p === 1) return Infinity + + // Initial guess + let k_init: integer + if (this.lambda > 10) { + const sigma = sqrt(this.lambda) + k_init = floor(this.lambda + sigma * normalInverseCDF(p)) + } else { + k_init = floor(this.lambda) + } + + // Binary search + let k_low = 0 + let k_high = max(k_init * 2, this.lambda + 10 * sqrt(this.lambda)) + + while (k_low < k_high) { + const k_mid = floor((k_low + k_high) / 2) + const cdf_mid = this.cdf(k_mid) + + if (cdf_mid < p) { + k_low = k_mid + 1 + } else { + k_high = k_mid + } + } + + return k_low + } + + random(): integer { + if (this.lambda < 10) { + return this.randomInverseTransform() + } else { + return this.randomPTRS() + } + } + + private randomInverseTransform(): integer { + const L = exp(-this.lambda) + let k = 0 + let p = 1.0 + + do { + k++ + p *= randomUniform() + } while (p > L) + + return k - 1 + } + + private randomPTRS(): integer { + const smu = sqrt(this.lambda) + const b = 0.931 + 2.53 * smu + const a = -0.059 + 0.02483 * b + const inv_alpha = 1.1239 + 1.1328 / (b - 3.4) + const v_r = 0.9277 - 3.6224 / (b - 2) + + while (true) { + const u = randomUniform() - 0.5 + const v = randomUniform() + const us = 0.5 - abs(u) + const k = floor((2 * a / us + b) * u + this.lambda + 0.43) + + if (us >= 0.07919 && v <= v_r) return k + if (k < 0 || (us < 0.013 && v > us)) continue + + const logRatio = (k + 0.5) * log(this.lambda / k) - this.lambda - k + + log(k) - 0.5 * log(2 * PI * k) + + if (log(v * inv_alpha / (a / (us * us) + b)) <= logRatio) { + return k + } + } + } +} +``` + +--- + +## Task 4.2: Binomial Distribution + +### Mathematical Formulas + +**Probability Mass Function (PMF)**: +``` +P(X = k) = C(n, k) * p^k * (1-p)^(n-k) + +where: +- n: number of trials (positive integer) +- k: number of successes (0 ≤ k ≤ n) +- p: success probability (0 ≤ p ≤ 1) +- C(n, k) = n! / (k! * (n-k)!) +``` + +**Cumulative Distribution Function (CDF)**: +``` +F(k) = P(X ≤ k) = Σ(i=0 to k) C(n, i) * p^i * (1-p)^(n-i) + = I_(1-p)(n-k, k+1) (using regularized incomplete beta) + +where I_x(a, b) is the regularized incomplete beta function +``` + +**Inverse CDF**: +``` +Q(p) = min{k : F(k) ≥ p} +``` + +### Algorithms + +#### PMF Computation + +**Method**: Logarithmic computation with binomial coefficient +``` +Algorithm: binomialPMF(k, n, p) +Input: k (0 ≤ k ≤ n), n (trials), p (probability) +Output: P(X = k) + +1. Validate inputs: + - If k < 0 or k > n: return 0 + - If p < 0 or p > 1: error + - If n < 0: error + +2. Handle edge cases: + - If p = 0: return (k = 0) ? 1 : 0 + - If p = 1: return (k = n) ? 1 : 0 + - If k = 0: return (1 - p)^n + - If k = n: return p^n + +3. Compute using logarithms: + log_binom_coeff = log_gamma(n + 1) - log_gamma(k + 1) - log_gamma(n - k + 1) + log_pmf = log_binom_coeff + k * log(p) + (n - k) * log(1 - p) + +4. Return exp(log_pmf) + +Optimization: +- Use recurrence for sequential k values: + P(k) = P(k-1) * ((n - k + 1) / k) * (p / (1 - p)) +``` + +#### CDF Computation + +**Method**: Incomplete Beta Function +``` +Algorithm: binomialCDF(k, n, p) +Input: k (integer), n (trials), p (probability) +Output: P(X ≤ k) + +1. If k < 0: return 0 +2. If k >= n: return 1 + +3. Handle edge cases: + - If p = 0: return 1 + - If p = 1: return (k = n) ? 1 : 0 + +4. Use regularized incomplete beta relation: + F(k) = 1 - I_p(k + 1, n - k) + + where I_p(a, b) = betaInc(p, a, b) / beta(a, b) + +5. For numerical stability: + if p < 0.5: + return 1 - incompleteBeta(p, k + 1, n - k) + else: + return incompleteBeta(1 - p, n - k, k + 1) + +Alternative for small n: +- Direct summation with recurrence relation +``` + +#### Inverse CDF (Quantile) + +**Method**: Binary search with smart initialization +``` +Algorithm: binomialInverseCDF(prob, n, p) +Input: prob ∈ [0, 1], n (trials), p (success probability) +Output: smallest k such that P(X ≤ k) ≥ prob + +1. Validate: + - If prob < 0 or prob > 1: error + - If prob = 0: return 0 + - If prob = 1: return n + +2. Initial guess using Normal approximation: + if n * p * (1 - p) > 9: + mu = n * p + sigma = sqrt(n * p * (1 - p)) + k_init = floor(mu + sigma * norm_inv_cdf(prob)) + k_init = clamp(k_init, 0, n) + else: + k_init = floor(n * p) + +3. Binary search: + k_low = 0 + k_high = n + + while k_low < k_high: + k_mid = floor((k_low + k_high) / 2) + cdf_mid = binomialCDF(k_mid, n, p) + + if cdf_mid < prob: + k_low = k_mid + 1 + else: + k_high = k_mid + +4. Return k_low +``` + +#### Random Sampling + +**Method 1**: Inverse Transform (small n) +``` +Algorithm: binomialRandom_InverseTransform(n, p) +Input: n ≤ 20, p ∈ [0, 1] +Output: Random binomial variate + +1. sum = 0 +2. for i = 0 to n - 1: + if uniform(0, 1) < p: + sum++ + +3. return sum +``` + +**Method 2**: BTPE Algorithm (large n) +``` +Algorithm: binomialRandom_BTPE(n, p) +Input: n > 20, p ∈ [0, 1] +Output: Random binomial variate + +// BTPE: Binomial Triangle Parallelogram Exponential +// From "The Computer Generation of Binomial Random Variates" +// by Voratas Kachitvichyanukul and Bruce W. Schmeiser + +1. Setup (computed once): + m = floor((n + 1) * p) + r = p / (1 - p) + nr = (n + 1) * r + npq = n * p * (1 - p) + + // Constants for BTPE + sqrt_npq = sqrt(npq) + b = 1.15 + 2.53 * sqrt_npq + a = -0.0873 + 0.0248 * b + 0.01 * p + c = n * p + 0.5 + alpha = (2.83 + 5.1 / b) * sqrt_npq + v_r = 0.92 - 4.2 / b + u_rv_r = 0.86 * v_r + +2. Loop until acceptance: + while true: + v = uniform(0, 1) + + if v <= u_rv_r: + u = v / v_r - 0.43 + return floor((2 * a / (0.5 - abs(u)) + b) * u + c) + + if v >= v_r: + u = uniform(0, 1) - 0.5 + else: + u = v / v_r - 0.93 + u = sign(u) * 0.5 - u + v = uniform(0, 1) * v_r + + us = 0.5 - abs(u) + k = floor((2 * a / us + b) * u + c) + + if k < 0 or k > n: + continue + + v = v * alpha / (a / (us * us) + b) + + // Quick acceptance + km = abs(k - m) + if km <= 15: + f = 1.0 + if m < k: + for i = m + 1 to k: + f *= (nr / i - r) + else if m > k: + for i = k + 1 to m: + f /= (nr / i - r) + + if v <= f: + return k + + // Squeeze acceptance + v = log(v) + rho = (km / npq) * (((km / 3.0 + 0.625) * km + 1.0 / 6.0) / npq + 0.5) + t = -km * km / (2.0 * npq) + + if v < t - rho: + return k + if v > t + rho: + continue + + // Final acceptance + nm = n - m + 1 + h = (m + 0.5) * log((m + 1) / (r * nm)) + + stirlingCorrection(m) + stirlingCorrection(n - m) + + nk = n - k + 1 + h_k = (k + 0.5) * log((k + 1) / (r * nk)) + + stirlingCorrection(k) + stirlingCorrection(n - k) + + if v <= h - h_k: + return k +``` + +**Stirling Correction Helper**: +``` +function stirlingCorrection(n: integer): number { + // Correction term for Stirling's approximation + const coeffs = [ + 0.08106146679532726, + -0.000595238095238095238, + 0.000793650793650793651, + -0.002777777777777777778, + 0.08333333333333333333 + ] + + if (n > 30) { + const nn = n * n + return coeffs[4] / n - coeffs[3] / (3 * nn) + } + + // Precomputed table for n ≤ 30 + const table = [0, 0.0810614667, 0.0413406959, ...] // Full table + return table[n] +} +``` + +### Complete Pseudocode + +```typescript +class BinomialDistribution { + private n: integer + private p: number + private q: number // 1 - p + private btpe: BTPEContext | null + + constructor(n: integer, p: number) { + if (n < 0) throw Error("n must be non-negative") + if (p < 0 || p > 1) throw Error("p must be in [0, 1]") + + this.n = n + this.p = p + this.q = 1 - p + + // Precompute BTPE constants for n > 20 + if (n > 20) { + this.btpe = this.setupBTPE() + } + } + + pmf(k: integer): number { + if (k < 0 || k > this.n) return 0 + if (this.p === 0) return (k === 0) ? 1 : 0 + if (this.p === 1) return (k === this.n) ? 1 : 0 + + // Logarithmic computation + const logBinomCoeff = logGamma(this.n + 1) + - logGamma(k + 1) + - logGamma(this.n - k + 1) + const logPMF = logBinomCoeff + + k * log(this.p) + + (this.n - k) * log(this.q) + + return exp(logPMF) + } + + cdf(k: integer): number { + if (k < 0) return 0 + if (k >= this.n) return 1 + if (this.p === 0) return 1 + if (this.p === 1) return (k === this.n) ? 1 : 0 + + // Use incomplete beta relation + if (this.p < 0.5) { + return 1 - incompleteBetaReg(this.p, k + 1, this.n - k) + } else { + return incompleteBetaReg(this.q, this.n - k, k + 1) + } + } + + inverseCDF(prob: number): integer { + if (prob < 0 || prob > 1) throw Error("prob must be in [0, 1]") + if (prob === 0) return 0 + if (prob === 1) return this.n + + // Initial guess + let k_init: integer + const npq = this.n * this.p * this.q + + if (npq > 9) { + const sigma = sqrt(npq) + k_init = floor(this.n * this.p + sigma * normalInverseCDF(prob)) + k_init = clamp(k_init, 0, this.n) + } else { + k_init = floor(this.n * this.p) + } + + // Binary search + let k_low = 0 + let k_high = this.n + + while (k_low < k_high) { + const k_mid = floor((k_low + k_high) / 2) + const cdf_mid = this.cdf(k_mid) + + if (cdf_mid < prob) { + k_low = k_mid + 1 + } else { + k_high = k_mid + } + } + + return k_low + } + + random(): integer { + if (this.n <= 20) { + // Simple method for small n + let sum = 0 + for (let i = 0; i < this.n; i++) { + if (randomUniform() < this.p) sum++ + } + return sum + } else { + return this.randomBTPE() + } + } + + private setupBTPE(): BTPEContext { + const m = floor((this.n + 1) * this.p) + const r = this.p / this.q + const npq = this.n * this.p * this.q + const sqrt_npq = sqrt(npq) + + return { + m, + r, + nr: (this.n + 1) * r, + npq, + sqrt_npq, + b: 1.15 + 2.53 * sqrt_npq, + a: -0.0873 + 0.0248 * (1.15 + 2.53 * sqrt_npq) + 0.01 * this.p, + c: this.n * this.p + 0.5, + alpha: (2.83 + 5.1 / (1.15 + 2.53 * sqrt_npq)) * sqrt_npq, + v_r: 0.92 - 4.2 / (1.15 + 2.53 * sqrt_npq) + } + } + + private randomBTPE(): integer { + const ctx = this.btpe! + const u_rv_r = 0.86 * ctx.v_r + + while (true) { + let v = randomUniform() + let u: number + + if (v <= u_rv_r) { + u = v / ctx.v_r - 0.43 + return floor((2 * ctx.a / (0.5 - abs(u)) + ctx.b) * u + ctx.c) + } + + if (v >= ctx.v_r) { + u = randomUniform() - 0.5 + } else { + u = v / ctx.v_r - 0.93 + u = sign(u) * 0.5 - u + v = randomUniform() * ctx.v_r + } + + const us = 0.5 - abs(u) + const k = floor((2 * ctx.a / us + ctx.b) * u + ctx.c) + + if (k < 0 || k > this.n) continue + + v = v * ctx.alpha / (ctx.a / (us * us) + ctx.b) + + // Acceptance tests... + const km = abs(k - ctx.m) + + if (km <= 15) { + let f = 1.0 + if (ctx.m < k) { + for (let i = ctx.m + 1; i <= k; i++) { + f *= (ctx.nr / i - ctx.r) + } + } else if (ctx.m > k) { + for (let i = k + 1; i <= ctx.m; i++) { + f /= (ctx.nr / i - ctx.r) + } + } + + if (v <= f) return k + } + + v = log(v) + const rho = (km / ctx.npq) * (((km / 3.0 + 0.625) * km + 1.0/6.0) / ctx.npq + 0.5) + const t = -km * km / (2.0 * ctx.npq) + + if (v < t - rho) return k + if (v > t + rho) continue + + const nm = this.n - ctx.m + 1 + const h = (ctx.m + 0.5) * log((ctx.m + 1) / (ctx.r * nm)) + + stirlingCorrection(ctx.m) + + stirlingCorrection(this.n - ctx.m) + + const nk = this.n - k + 1 + const h_k = (k + 0.5) * log((k + 1) / (ctx.r * nk)) + + stirlingCorrection(k) + + stirlingCorrection(this.n - k) + + if (v <= h - h_k) return k + } + } +} +``` + +--- + +## Task 4.3: Negative Binomial Distribution + +### Mathematical Formulas + +**Probability Mass Function (PMF)**: +``` +P(X = k) = C(k + r - 1, k) * p^r * (1-p)^k + +where: +- k = 0, 1, 2, ... (number of failures before r-th success) +- r > 0 (number of successes, can be non-integer) +- p ∈ (0, 1] (success probability) +- C(k + r - 1, k) = Γ(k + r) / (Γ(r) * k!) +``` + +**Alternative Parameterization** (mean μ, dispersion r): +``` +P(X = k) = [Γ(k + r) / (Γ(r) * k!)] * (r/(r+μ))^r * (μ/(r+μ))^k + +where μ = r * (1-p) / p +``` + +**Cumulative Distribution Function (CDF)**: +``` +F(k) = P(X ≤ k) = I_p(r, k+1) + +where I_p(a, b) is the regularized incomplete beta function +``` + +**Inverse CDF**: +``` +Q(prob) = min{k : F(k) ≥ prob} +``` + +### Algorithms + +#### PMF Computation + +``` +Algorithm: negativeBinomialPMF(k, r, p) +Input: k ≥ 0 (failures), r > 0 (successes), p ∈ (0, 1] +Output: P(X = k) + +1. Validate: + - If k < 0: return 0 + - If r <= 0: error "r must be positive" + - If p <= 0 or p > 1: error "p must be in (0, 1]" + +2. Special cases: + - If p = 1: return (k = 0) ? 1 : 0 + - If k = 0: return p^r + +3. Compute using logarithms: + log_binom = log_gamma(k + r) - log_gamma(r) - log_gamma(k + 1) + log_pmf = log_binom + r * log(p) + k * log(1 - p) + +4. Return exp(log_pmf) + +Recurrence relation for sequential k: +P(k) = P(k-1) * ((k + r - 1) / k) * (1 - p) +``` + +#### CDF Computation + +``` +Algorithm: negativeBinomialCDF(k, r, p) +Input: k ≥ 0, r > 0, p ∈ (0, 1] +Output: P(X ≤ k) + +1. If k < 0: return 0 +2. If p = 1: return 1 + +3. Use regularized incomplete beta: + return incompleteBetaReg(p, r, k + 1) + +Alternative for integer r and small k: +- Direct summation using recurrence relation +``` + +#### Inverse CDF + +``` +Algorithm: negativeBinomialInverseCDF(prob, r, p) +Input: prob ∈ [0, 1], r > 0, p ∈ (0, 1] +Output: smallest k such that F(k) ≥ prob + +1. Validate: + - If prob < 0 or prob > 1: error + - If prob = 0: return 0 + - If prob = 1: return Infinity + +2. Initial guess using mean/variance: + mean = r * (1 - p) / p + variance = r * (1 - p) / (p * p) + + if variance > mean: // Overdispersed + k_init = floor(mean + sqrt(variance) * norm_inv_cdf(prob)) + else: + k_init = floor(mean) + +3. Binary search: + k_low = 0 + k_high = max(k_init * 3, mean + 10 * sqrt(variance)) + + while k_low < k_high: + k_mid = floor((k_low + k_high) / 2) + if negativeBinomialCDF(k_mid, r, p) < prob: + k_low = k_mid + 1 + else: + k_high = k_mid + +4. Return k_low +``` + +#### Random Sampling + +**Method 1**: Gamma-Poisson Mixture +``` +Algorithm: negativeBinomialRandom_GammaPoisson(r, p) +Input: r > 0, p ∈ (0, 1] +Output: Random negative binomial variate + +// Negative binomial is a Poisson-Gamma mixture: +// If λ ~ Gamma(r, (1-p)/p), then X|λ ~ Poisson(λ) + +1. theta = (1 - p) / p +2. lambda = gammaRandom(r, theta) // Gamma(shape=r, scale=theta) +3. return poissonRandom(lambda) +``` + +**Method 2**: Direct Counting (integer r) +``` +Algorithm: negativeBinomialRandom_Counting(r, p) +Input: r (positive integer), p ∈ (0, 1] +Output: Random negative binomial variate + +// Count failures until r successes +1. failures = 0 +2. successes = 0 + +3. while successes < r: + if uniform(0, 1) < p: + successes++ + else: + failures++ + +4. return failures +``` + +### Complete Pseudocode + +```typescript +class NegativeBinomialDistribution { + private r: number + private p: number + private mean: number + private variance: number + + constructor(r: number, p: number) { + if (r <= 0) throw Error("r must be positive") + if (p <= 0 || p > 1) throw Error("p must be in (0, 1]") + + this.r = r + this.p = p + this.mean = r * (1 - p) / p + this.variance = this.mean / p + } + + pmf(k: integer): number { + if (k < 0) return 0 + if (this.p === 1) return (k === 0) ? 1 : 0 + if (k === 0) return pow(this.p, this.r) + + const logBinom = logGamma(k + this.r) + - logGamma(this.r) + - logGamma(k + 1) + const logPMF = logBinom + + this.r * log(this.p) + + k * log(1 - this.p) + + return exp(logPMF) + } + + cdf(k: integer): number { + if (k < 0) return 0 + if (this.p === 1) return 1 + + return incompleteBetaReg(this.p, this.r, k + 1) + } + + inverseCDF(prob: number): integer { + if (prob < 0 || prob > 1) throw Error("prob must be in [0, 1]") + if (prob === 0) return 0 + if (prob === 1) return Infinity + + const sigma = sqrt(this.variance) + const k_init = floor(this.mean + sigma * normalInverseCDF(prob)) + + let k_low = 0 + let k_high = max(k_init * 3, this.mean + 10 * sigma) + + while (k_low < k_high) { + const k_mid = floor((k_low + k_high) / 2) + if (this.cdf(k_mid) < prob) { + k_low = k_mid + 1 + } else { + k_high = k_mid + } + } + + return k_low + } + + random(): integer { + // Gamma-Poisson mixture method + const theta = (1 - this.p) / this.p + const lambda = randomGamma(this.r, theta) + return randomPoisson(lambda) + } +} +``` + +--- + +## Task 4.4: Geometric Distribution + +### Mathematical Formulas + +**Probability Mass Function (PMF)**: +``` +P(X = k) = (1-p)^k * p + +where: +- k = 0, 1, 2, ... (number of failures before first success) +- p ∈ (0, 1] (success probability) + +Alternative parameterization (trials until success): +P(X = k) = (1-p)^(k-1) * p, k = 1, 2, 3, ... +``` + +**Cumulative Distribution Function (CDF)**: +``` +F(k) = P(X ≤ k) = 1 - (1-p)^(k+1) +``` + +**Inverse CDF**: +``` +Q(prob) = ⌈log(1-prob) / log(1-p) - 1⌉ + = ⌈log(1-prob) / log(1-p)⌉ - 1 (failures before success) +``` + +**Mean**: μ = (1-p) / p +**Variance**: σ² = (1-p) / p² + +### Algorithms + +#### PMF Computation + +``` +Algorithm: geometricPMF(k, p) +Input: k ≥ 0 (number of failures), p ∈ (0, 1] +Output: P(X = k) + +1. If k < 0: return 0 +2. If p <= 0 or p > 1: error +3. If p = 1: return (k = 0) ? 1 : 0 + +4. Direct computation: + return pow(1 - p, k) * p + +For numerical stability with large k: + log_pmf = k * log(1 - p) + log(p) + return exp(log_pmf) +``` + +#### CDF Computation + +``` +Algorithm: geometricCDF(k, p) +Input: k ≥ 0, p ∈ (0, 1] +Output: P(X ≤ k) + +1. If k < 0: return 0 +2. If p = 1: return 1 + +3. Direct formula: + return 1 - pow(1 - p, k + 1) + +For numerical stability: + if p > 0.5: + return 1 - pow(1 - p, k + 1) + else: + return 1 - exp((k + 1) * log(1 - p)) +``` + +#### Inverse CDF + +``` +Algorithm: geometricInverseCDF(prob, p) +Input: prob ∈ [0, 1], p ∈ (0, 1] +Output: smallest k such that F(k) ≥ prob + +1. Validate: + - If prob < 0 or prob > 1: error + - If prob = 0: return 0 + - If prob = 1: return Infinity + +2. Analytical formula: + if prob = 1: + return Infinity + + k = ceil(log(1 - prob) / log(1 - p)) - 1 + + // Ensure k is non-negative + return max(k, 0) + +Numerical stability: + if p > 0.5: + k = ceil(log(1 - prob) / log(1 - p)) - 1 + else: + k = ceil(log1p(-prob) / log1p(-p)) - 1 + + return max(k, 0) +``` + +#### Random Sampling + +**Method 1**: Inverse Transform +``` +Algorithm: geometricRandom_InverseTransform(p) +Input: p ∈ (0, 1] +Output: Random geometric variate + +1. u = uniform(0, 1) +2. return floor(log(1 - u) / log(1 - p)) + +Numerical stability: + return floor(log(uniform(0, 1)) / log1p(-p)) +``` + +**Method 2**: Direct Simulation +``` +Algorithm: geometricRandom_Direct(p) +Input: p ∈ (0, 1] +Output: Random geometric variate + +1. k = 0 +2. while uniform(0, 1) >= p: + k++ +3. return k +``` + +### Complete Pseudocode + +```typescript +class GeometricDistribution { + private p: number + private log1mp: number // log(1 - p) + + constructor(p: number) { + if (p <= 0 || p > 1) throw Error("p must be in (0, 1]") + + this.p = p + this.log1mp = log1p(-p) // Numerically stable log(1-p) + } + + pmf(k: integer): number { + if (k < 0) return 0 + if (this.p === 1) return (k === 0) ? 1 : 0 + + // Use logarithmic computation for stability + if (k > 100) { + return exp(k * this.log1mp + log(this.p)) + } + + return pow(1 - this.p, k) * this.p + } + + cdf(k: integer): number { + if (k < 0) return 0 + if (this.p === 1) return 1 + + // F(k) = 1 - (1-p)^(k+1) + return -expm1((k + 1) * this.log1mp) // 1 - exp((k+1)*log(1-p)) + } + + inverseCDF(prob: number): integer { + if (prob < 0 || prob > 1) throw Error("prob must be in [0, 1]") + if (prob === 0) return 0 + if (prob === 1) return Infinity + + // Q(p) = ceil(log(1-prob) / log(1-p)) - 1 + const k = ceil(log1p(-prob) / this.log1mp) - 1 + + return max(k, 0) + } + + random(): integer { + // Inverse transform method + const u = randomUniform() + return floor(log(u) / this.log1mp) + } + + mean(): number { + return (1 - this.p) / this.p + } + + variance(): number { + return (1 - this.p) / (this.p * this.p) + } +} +``` + +--- + +## Task 4.5: Hypergeometric Distribution + +### Mathematical Formulas + +**Probability Mass Function (PMF)**: +``` +P(X = k) = [C(K, k) * C(N-K, n-k)] / C(N, n) + +where: +- N: population size (positive integer) +- K: number of success states in population (0 ≤ K ≤ N) +- n: number of draws (0 ≤ n ≤ N) +- k: number of observed successes (max(0, n-N+K) ≤ k ≤ min(n, K)) +- C(a, b) = binomial coefficient = a! / (b! * (a-b)!) +``` + +**Cumulative Distribution Function (CDF)**: +``` +F(k) = P(X ≤ k) = Σ(i=k_min to k) P(X = i) + +where k_min = max(0, n - N + K) +``` + +**Inverse CDF**: +``` +Q(prob) = min{k : F(k) ≥ prob} +``` + +**Mean**: μ = n * K / N +**Variance**: σ² = n * (K/N) * (1 - K/N) * (N-n)/(N-1) + +### Algorithms + +#### PMF Computation + +``` +Algorithm: hypergeometricPMF(k, N, K, n) +Input: k (successes), N (population), K (success states), n (draws) +Output: P(X = k) + +1. Validate: + - If N < 0 or K < 0 or n < 0: error + - If K > N or n > N: error + +2. Check support: + k_min = max(0, n - N + K) + k_max = min(n, K) + + if k < k_min or k > k_max: return 0 + +3. Special cases: + - If n = 0: return (k = 0) ? 1 : 0 + - If K = 0: return (k = 0) ? 1 : 0 + - If K = N: return (k = n) ? 1 : 0 + +4. Compute using log binomial coefficients: + log_pmf = log_binom(K, k) + + log_binom(N - K, n - k) + - log_binom(N, n) + + return exp(log_pmf) + +Helper function: +log_binom(n, k): + if k < 0 or k > n: return -Infinity + if k = 0 or k = n: return 0 + return log_gamma(n + 1) - log_gamma(k + 1) - log_gamma(n - k + 1) + +Optimization for sequential k (recurrence): +P(k) = P(k-1) * [(K - k + 1) * (n - k + 1)] / [k * (N - K - n + k)] +``` + +#### CDF Computation + +``` +Algorithm: hypergeometricCDF(k, N, K, n) +Input: k, N, K, n +Output: P(X ≤ k) + +1. k_min = max(0, n - N + K) +2. k_max = min(n, K) + +3. If k < k_min: return 0 +4. If k >= k_max: return 1 + +5. Direct summation with recurrence: + k_start = k_min + k_end = min(k, k_max) + + sum = 0 + pmf = hypergeometricPMF(k_start, N, K, n) + sum = pmf + + for i = k_start + 1 to k_end: + // Use recurrence + pmf *= [(K - i + 1) * (n - i + 1)] / [i * (N - K - n + i)] + sum += pmf + + if pmf < sum * EPSILON: + break + + return min(sum, 1.0) + +Optimization: +- Start from mode if k > mode +- Use symmetry: P(X ≤ k) = 1 - P(X > k) when beneficial +``` + +#### Inverse CDF + +``` +Algorithm: hypergeometricInverseCDF(prob, N, K, n) +Input: prob ∈ [0, 1], N, K, n +Output: smallest k such that F(k) ≥ prob + +1. Validate: + - If prob < 0 or prob > 1: error + +2. k_min = max(0, n - N + K) +3. k_max = min(n, K) + +4. If prob = 0: return k_min +5. If prob = 1: return k_max + +6. Initial guess: + mean = n * K / N + k_init = round(mean) + +7. Binary search: + k_low = k_min + k_high = k_max + + while k_low < k_high: + k_mid = floor((k_low + k_high) / 2) + cdf_mid = hypergeometricCDF(k_mid, N, K, n) + + if cdf_mid < prob: + k_low = k_mid + 1 + else: + k_high = k_mid + +8. Return k_low +``` + +#### Random Sampling + +**Method 1**: Urn Model (small n) +``` +Algorithm: hypergeometricRandom_UrnModel(N, K, n) +Input: N (population), K (successes), n (draws) +Output: Random hypergeometric variate + +// Simulate drawing without replacement +1. successes = 0 +2. remaining_success = K +3. remaining_total = N + +4. for i = 0 to n - 1: + prob_success = remaining_success / remaining_total + + if uniform(0, 1) < prob_success: + successes++ + remaining_success-- + + remaining_total-- + +5. return successes +``` + +**Method 2**: Acceptance-Rejection (large N) +``` +Algorithm: hypergeometricRandom_Rejection(N, K, n) +Input: N, K, n +Output: Random hypergeometric variate + +// Use binomial approximation with acceptance-rejection +1. p = K / N +2. mode = floor((n + 1) * (K + 1) / (N + 2)) +3. max_pmf = hypergeometricPMF(mode, N, K, n) + +4. Loop until acceptance: + while true: + // Proposal from binomial + k = binomialRandom(n, p) + + // Acceptance probability + hyp_pmf = hypergeometricPMF(k, N, K, n) + bin_pmf = binomialPMF(k, n, p) + + acceptance_prob = hyp_pmf / (max_pmf * bin_pmf / max_pmf) + + if uniform(0, 1) < acceptance_prob: + return k +``` + +**Method 3**: Ratio of Uniforms (alternative) +``` +Algorithm: hypergeometricRandom_HRUA(N, K, n) +Input: N, K, n +Output: Random hypergeometric variate + +// H2PE algorithm (Hypergeometric 2-Point Exact) +// Similar to BTPE for binomial + +1. p = K / N +2. m = floor((n + 1) * p) +3. Setup constants based on N, K, n... + +4. Loop with acceptance tests: + [Complex algorithm - see reference implementation] + +5. return accepted k +``` + +### Complete Pseudocode + +```typescript +class HypergeometricDistribution { + private N: integer // Population size + private K: integer // Success states in population + private n: integer // Number of draws + private k_min: integer + private k_max: integer + private mean: number + private variance: number + + constructor(N: integer, K: integer, n: integer) { + if (N < 0 || K < 0 || n < 0) { + throw Error("N, K, n must be non-negative") + } + if (K > N || n > N) { + throw Error("Invalid parameters: K > N or n > N") + } + + this.N = N + this.K = K + this.n = n + this.k_min = max(0, n - N + K) + this.k_max = min(n, K) + + const p = K / N + this.mean = n * p + this.variance = n * p * (1 - p) * (N - n) / (N - 1) + } + + pmf(k: integer): number { + if (k < this.k_min || k > this.k_max) return 0 + + if (this.n === 0) return (k === 0) ? 1 : 0 + if (this.K === 0) return (k === 0) ? 1 : 0 + if (this.K === this.N) return (k === this.n) ? 1 : 0 + + // Logarithmic computation + const logPMF = this.logBinom(this.K, k) + + this.logBinom(this.N - this.K, this.n - k) + - this.logBinom(this.N, this.n) + + return exp(logPMF) + } + + private logBinom(n: integer, k: integer): number { + if (k < 0 || k > n) return -Infinity + if (k === 0 || k === n) return 0 + + return logGamma(n + 1) - logGamma(k + 1) - logGamma(n - k + 1) + } + + cdf(k: integer): number { + if (k < this.k_min) return 0 + if (k >= this.k_max) return 1 + + let sum = 0 + let pmf = this.pmf(this.k_min) + sum = pmf + + for (let i = this.k_min + 1; i <= min(k, this.k_max); i++) { + // Recurrence relation + pmf *= ((this.K - i + 1) * (this.n - i + 1)) / + (i * (this.N - this.K - this.n + i)) + sum += pmf + + if (pmf < sum * Number.EPSILON) break + } + + return min(sum, 1.0) + } + + inverseCDF(prob: number): integer { + if (prob < 0 || prob > 1) throw Error("prob must be in [0, 1]") + if (prob === 0) return this.k_min + if (prob === 1) return this.k_max + + let k_low = this.k_min + let k_high = this.k_max + + while (k_low < k_high) { + const k_mid = floor((k_low + k_high) / 2) + const cdf_mid = this.cdf(k_mid) + + if (cdf_mid < prob) { + k_low = k_mid + 1 + } else { + k_high = k_mid + } + } + + return k_low + } + + random(): integer { + if (this.n <= 20) { + // Urn model for small n + return this.randomUrnModel() + } else { + // Acceptance-rejection for large n + return this.randomRejection() + } + } + + private randomUrnModel(): integer { + let successes = 0 + let remaining_success = this.K + let remaining_total = this.N + + for (let i = 0; i < this.n; i++) { + const prob_success = remaining_success / remaining_total + + if (randomUniform() < prob_success) { + successes++ + remaining_success-- + } + + remaining_total-- + } + + return successes + } + + private randomRejection(): integer { + const p = this.K / this.N + const mode = floor((this.n + 1) * (this.K + 1) / (this.N + 2)) + const max_pmf = this.pmf(mode) + + while (true) { + const k = randomBinomial(this.n, p) + + if (k < this.k_min || k > this.k_max) continue + + const hyp_pmf = this.pmf(k) + const bin_pmf = binomialPMF(k, this.n, p) + + const acceptance_prob = hyp_pmf / (max_pmf * bin_pmf / max_pmf) + + if (randomUniform() < acceptance_prob) { + return k + } + } + } +} +``` + +--- + +## Task 4.6: Uniform Distribution (Continuous) + +### Mathematical Formulas + +**Probability Density Function (PDF)**: +``` +f(x) = 1 / (b - a) for a ≤ x ≤ b + = 0 otherwise + +where: +- a: lower bound +- b: upper bound (b > a) +``` + +**Cumulative Distribution Function (CDF)**: +``` +F(x) = 0 for x < a + = (x - a) / (b - a) for a ≤ x ≤ b + = 1 for x > b +``` + +**Inverse CDF** (Quantile Function): +``` +Q(p) = a + p * (b - a) for p ∈ [0, 1] +``` + +**Mean**: μ = (a + b) / 2 +**Variance**: σ² = (b - a)² / 12 + +### Algorithms + +``` +Algorithm: uniformPDF(x, a, b) +Input: x, a (lower bound), b (upper bound) +Output: f(x) + +1. If b <= a: error "b must be greater than a" +2. If x < a or x > b: return 0 +3. Return 1 / (b - a) +``` + +``` +Algorithm: uniformCDF(x, a, b) +Input: x, a, b +Output: F(x) + +1. If b <= a: error +2. If x < a: return 0 +3. If x > b: return 1 +4. Return (x - a) / (b - a) +``` + +``` +Algorithm: uniformInverseCDF(p, a, b) +Input: p ∈ [0, 1], a, b +Output: Q(p) + +1. If p < 0 or p > 1: error +2. If b <= a: error +3. Return a + p * (b - a) +``` + +``` +Algorithm: uniformRandom(a, b) +Input: a, b +Output: Random uniform variate in [a, b] + +1. u = uniform(0, 1) +2. Return a + u * (b - a) +``` + +### Complete Pseudocode + +```typescript +class UniformDistribution { + private a: number + private b: number + private range: number + + constructor(a: number, b: number) { + if (b <= a) throw Error("b must be greater than a") + + this.a = a + this.b = b + this.range = b - a + } + + pdf(x: number): number { + if (x < this.a || x > this.b) return 0 + return 1 / this.range + } + + cdf(x: number): number { + if (x < this.a) return 0 + if (x > this.b) return 1 + return (x - this.a) / this.range + } + + inverseCDF(p: number): number { + if (p < 0 || p > 1) throw Error("p must be in [0, 1]") + return this.a + p * this.range + } + + random(): number { + return this.a + randomUniform() * this.range + } + + mean(): number { + return (this.a + this.b) / 2 + } + + variance(): number { + return this.range * this.range / 12 + } +} +``` + +--- + +## Task 4.7: Cauchy Distribution + +### Mathematical Formulas + +**Probability Density Function (PDF)**: +``` +f(x) = 1 / [π * γ * (1 + ((x - x₀)/γ)²)] + +where: +- x₀: location parameter (median) +- γ > 0: scale parameter (half-width at half-maximum) +``` + +**Cumulative Distribution Function (CDF)**: +``` +F(x) = (1/π) * arctan((x - x₀)/γ) + 1/2 +``` + +**Inverse CDF** (Quantile Function): +``` +Q(p) = x₀ + γ * tan(π * (p - 1/2)) +``` + +**Note**: Cauchy distribution has no defined mean or variance (heavy tails) + +### Algorithms + +``` +Algorithm: cauchyPDF(x, x0, gamma) +Input: x, x0 (location), gamma (scale > 0) +Output: f(x) + +1. If gamma <= 0: error "gamma must be positive" +2. z = (x - x0) / gamma +3. return 1 / (PI * gamma * (1 + z * z)) + +Numerical stability for large |x|: + if abs(z) > 1e8: + return 0 // Effectively zero in floating point +``` + +``` +Algorithm: cauchyCDF(x, x0, gamma) +Input: x, x0, gamma +Output: F(x) + +1. If gamma <= 0: error +2. z = (x - x0) / gamma +3. return atan(z) / PI + 0.5 + +Numerical stability: + Use atan2 for better precision + return atan2(x - x0, gamma) / PI + 0.5 +``` + +``` +Algorithm: cauchyInverseCDF(p, x0, gamma) +Input: p ∈ [0, 1], x0, gamma +Output: Q(p) + +1. If p < 0 or p > 1: error +2. If gamma <= 0: error + +3. Special cases: + - If p = 0: return -Infinity + - If p = 0.5: return x0 + - If p = 1: return Infinity + +4. return x0 + gamma * tan(PI * (p - 0.5)) + +Numerical stability near p = 0 or p = 1: + if p < 1e-10: return -Infinity + if p > 1 - 1e-10: return Infinity +``` + +``` +Algorithm: cauchyRandom(x0, gamma) +Input: x0, gamma +Output: Random Cauchy variate + +// Inverse transform method +1. u = uniform(0, 1) +2. return x0 + gamma * tan(PI * (u - 0.5)) + +Alternative (ratio of normals): +1. u1 = normal(0, 1) +2. u2 = normal(0, 1) +3. return x0 + gamma * (u1 / u2) +``` + +### Complete Pseudocode + +```typescript +class CauchyDistribution { + private x0: number + private gamma: number + + constructor(x0: number, gamma: number) { + if (gamma <= 0) throw Error("gamma must be positive") + + this.x0 = x0 + this.gamma = gamma + } + + pdf(x: number): number { + const z = (x - this.x0) / this.gamma + + // Numerical stability + if (abs(z) > 1e8) return 0 + + return 1 / (PI * this.gamma * (1 + z * z)) + } + + cdf(x: number): number { + // Using atan2 for better numerical stability + return atan2(x - this.x0, this.gamma) / PI + 0.5 + } + + inverseCDF(p: number): number { + if (p < 0 || p > 1) throw Error("p must be in [0, 1]") + + // Special cases + if (p === 0) return -Infinity + if (p === 0.5) return this.x0 + if (p === 1) return Infinity + + // Numerical stability near extremes + if (p < 1e-10) return -Infinity + if (p > 1 - 1e-10) return Infinity + + return this.x0 + this.gamma * tan(PI * (p - 0.5)) + } + + random(): number { + // Inverse transform method + const u = randomUniform() + return this.inverseCDF(u) + } + + median(): number { + return this.x0 + } + + // No mean or variance (undefined for Cauchy) +} +``` + +--- + +## Task 4.8: Logistic Distribution + +### Mathematical Formulas + +**Probability Density Function (PDF)**: +``` +f(x) = exp(-(x-μ)/s) / [s * (1 + exp(-(x-μ)/s))²] + = exp((x-μ)/s) / [s * (1 + exp((x-μ)/s))²] (alternative form) + +where: +- μ: location parameter (mean) +- s > 0: scale parameter +``` + +**Cumulative Distribution Function (CDF)**: +``` +F(x) = 1 / (1 + exp(-(x-μ)/s)) + = logistic((x-μ)/s) +``` + +**Inverse CDF** (Quantile Function): +``` +Q(p) = μ + s * log(p / (1-p)) + = μ + s * logit(p) +``` + +**Mean**: μ +**Variance**: σ² = s² * π² / 3 + +### Algorithms + +``` +Algorithm: logisticPDF(x, mu, s) +Input: x, mu (location), s (scale > 0) +Output: f(x) + +1. If s <= 0: error "s must be positive" + +2. z = (x - mu) / s + +3. Numerical stability: + if z > 0: + exp_z = exp(-z) + return exp_z / (s * (1 + exp_z)²) + else: + exp_z = exp(z) + return exp_z / (s * (1 + exp_z)²) + +Alternative using expit function: + p = expit(z) // 1 / (1 + exp(-z)) + return p * (1 - p) / s +``` + +``` +Algorithm: logisticCDF(x, mu, s) +Input: x, mu, s +Output: F(x) + +1. If s <= 0: error + +2. z = (x - mu) / s + +3. Use expit (numerically stable logistic function): + return expit(z) = 1 / (1 + exp(-z)) + +Numerical stability implementation: + if z >= 0: + return 1 / (1 + exp(-z)) + else: + exp_z = exp(z) + return exp_z / (1 + exp_z) +``` + +``` +Algorithm: logisticInverseCDF(p, mu, s) +Input: p ∈ [0, 1], mu, s +Output: Q(p) + +1. If p < 0 or p > 1: error +2. If s <= 0: error + +3. Special cases: + - If p = 0: return -Infinity + - If p = 0.5: return mu + - If p = 1: return Infinity + +4. Use logit function: + return mu + s * log(p / (1 - p)) + +Numerical stability: + if p < 1e-10: return -Infinity + if p > 1 - 1e-10: return Infinity + + // Use log1p for better precision near 0 and 1 + return mu + s * (log(p) - log1p(-p)) +``` + +``` +Algorithm: logisticRandom(mu, s) +Input: mu, s +Output: Random logistic variate + +// Inverse transform method +1. u = uniform(0, 1) +2. return logisticInverseCDF(u, mu, s) + +Direct formula: + return mu + s * log(u / (1 - u)) + +Numerical stability: + if u < 0.5: + return mu + s * (log(u) - log1p(-u)) + else: + return mu + s * (log(u) - log(1 - u)) +``` + +### Complete Pseudocode + +```typescript +class LogisticDistribution { + private mu: number + private s: number + + constructor(mu: number, s: number) { + if (s <= 0) throw Error("s must be positive") + + this.mu = mu + this.s = s + } + + pdf(x: number): number { + const z = (x - this.mu) / this.s + + // Use expit for numerical stability + const p = this.expit(z) + return p * (1 - p) / this.s + } + + cdf(x: number): number { + const z = (x - this.mu) / this.s + return this.expit(z) + } + + private expit(z: number): number { + // Numerically stable logistic function + if (z >= 0) { + return 1 / (1 + exp(-z)) + } else { + const exp_z = exp(z) + return exp_z / (1 + exp_z) + } + } + + inverseCDF(p: number): number { + if (p < 0 || p > 1) throw Error("p must be in [0, 1]") + + if (p === 0) return -Infinity + if (p === 0.5) return this.mu + if (p === 1) return Infinity + + // Numerical stability near extremes + if (p < 1e-10) return -Infinity + if (p > 1 - 1e-10) return Infinity + + // logit(p) = log(p / (1-p)) + return this.mu + this.s * (log(p) - log1p(-p)) + } + + random(): number { + const u = randomUniform() + + // Avoid exact 0 or 1 + const safe_u = clamp(u, 1e-10, 1 - 1e-10) + + return this.inverseCDF(safe_u) + } + + mean(): number { + return this.mu + } + + variance(): number { + return this.s * this.s * PI * PI / 3 + } + + median(): number { + return this.mu + } +} +``` + +--- + +## Task 4.9: Incomplete Beta Function + +### Mathematical Formulas + +**Incomplete Beta Function**: +``` +B(x; a, b) = ∫₀ˣ t^(a-1) * (1-t)^(b-1) dt + +Regularized form: +I_x(a, b) = B(x; a, b) / B(a, b) + = B(x; a, b) / [Γ(a) * Γ(b) / Γ(a+b)] +``` + +**Symmetry Relation**: +``` +I_x(a, b) = 1 - I_(1-x)(b, a) +``` + +**Continued Fraction Representation** (for I_x(a,b)): +``` +I_x(a, b) = [x^a * (1-x)^b] / [a * B(a, b)] * CF + +where CF (continued fraction) is computed using Lentz's method +``` + +### Algorithms + +#### Regularized Incomplete Beta - Main Algorithm + +``` +Algorithm: incompleteBetaReg(x, a, b) +Input: x ∈ [0, 1], a > 0, b > 0 +Output: I_x(a, b) + +1. Validate: + - If x < 0 or x > 1: error + - If a <= 0 or b <= 0: error + +2. Boundary cases: + - If x = 0: return 0 + - If x = 1: return 1 + +3. Use symmetry for efficiency: + if x > (a + 1) / (a + b + 2): + return 1 - incompleteBetaReg(1 - x, b, a) + +4. Compute logarithmic prefix: + log_beta = log_gamma(a) + log_gamma(b) - log_gamma(a + b) + log_prefix = a * log(x) + b * log(1 - x) - log_beta - log(a) + +5. If a > 3000 and b > 3000: + // Use asymptotic expansion for large a, b + return incompleteBetaAsymptotic(x, a, b) + +6. Choose method based on parameters: + if x < (a + 1) / (a + b + 2): + // Use continued fraction + cf = incompleteBetaCF(x, a, b) + return exp(log_prefix) * cf + else: + // Use series expansion + series = incompleteBetaSeries(x, a, b) + return exp(log_prefix) * series +``` + +#### Continued Fraction Method (Lentz's Algorithm) + +``` +Algorithm: incompleteBetaCF(x, a, b) +Input: x ∈ [0, 1], a > 0, b > 0 +Output: Continued fraction value for I_x(a, b) + +// Lentz's method for continued fraction evaluation +// CF = 1 / (1 + d₁ / (1 + d₂ / (1 + d₃ / ...))) +// where d_m are determined by recurrence relations + +1. Constants: + EPSILON = 1e-15 + TINY = 1e-30 + MAX_ITER = 200 + +2. Initialize: + qab = a + b + qap = a + 1 + qam = a - 1 + + c = 1.0 + d = 1.0 - qab * x / qap + + if abs(d) < TINY: d = TINY + d = 1.0 / d + h = d + +3. Iterate: + for m = 1 to MAX_ITER: + m2 = 2 * m + + // Even step + aa = m * (b - m) * x / ((qam + m2) * (a + m2)) + d = 1.0 + aa * d + if abs(d) < TINY: d = TINY + c = 1.0 + aa / c + if abs(c) < TINY: c = TINY + d = 1.0 / d + h *= d * c + + // Odd step + aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2)) + d = 1.0 + aa * d + if abs(d) < TINY: d = TINY + c = 1.0 + aa / c + if abs(c) < TINY: c = TINY + d = 1.0 / d + delta = d * c + h *= delta + + // Convergence test + if abs(delta - 1.0) < EPSILON: + return h + +4. If not converged: error "Continued fraction did not converge" +``` + +#### Series Expansion Method + +``` +Algorithm: incompleteBetaSeries(x, a, b) +Input: x ∈ [0, 1], a > 0, b > 0 +Output: Series expansion for I_x(a, b) + +// Use series expansion for small x or when CF is not efficient +// I_x(a,b) = (x^a / a) * Σ[(1-b)_n / (a+n)] * x^n + +1. Constants: + EPSILON = 1e-15 + MAX_ITER = 1000 + +2. Initialize: + sum = 1.0 / a + term = 1.0 / a + + apb = a + b + ap = a + +3. Iterate: + for n = 1 to MAX_ITER: + ap += 1 + term *= (n - b) * x / (ap - a) + sum += term + + if abs(term) < abs(sum) * EPSILON: + return sum * exp(a * log(x) + b * log(1-x) - logBeta(a, b)) + +4. If not converged: error "Series did not converge" +``` + +#### Inverse Incomplete Beta (Quantile) + +``` +Algorithm: incompleteBetaInverse(p, a, b) +Input: p ∈ [0, 1], a > 0, b > 0 +Output: x such that I_x(a, b) = p + +1. Validate: + - If p < 0 or p > 1: error + - If p = 0: return 0 + - If p = 1: return 1 + +2. Initial guess: + // Use various approximations based on a, b + + if a >= 1 and b >= 1: + // Wilson-Hilferty approximation + if p < 0.5: + pp = p + a1 = a - 0.5 + b1 = b - 0.5 + else: + pp = 1 - p + a1 = b - 0.5 + b1 = a - 0.5 + + t = sqrt(-2 * log(pp)) + x_init = (2.30753 + t * 0.27061) / (1 + t * (0.99229 + t * 0.04481)) + x_init = a1 / (a1 + b1 * exp(x_init)) + + if p < 0.5: + x = x_init + else: + x = 1 - x_init + + else: + // Simpler approximation for a < 1 or b < 1 + lna = log(a / (a + b)) + lnb = log(b / (a + b)) + t = exp(a * lna) / a + u = exp(b * lnb) / b + w = t + u + + if p < t / w: + x = pow(a * w * p, 1 / a) + else: + x = 1 - pow(b * w * (1 - p), 1 / b) + +3. Refine using Newton-Raphson: + for iter = 1 to 100: + // Compute I_x(a, b) and its derivative + beta_cdf = incompleteBetaReg(x, a, b) + error = beta_cdf - p + + // Derivative: d/dx I_x(a,b) = x^(a-1) * (1-x)^(b-1) / B(a,b) + beta_pdf = exp((a - 1) * log(x) + (b - 1) * log(1 - x) - logBeta(a, b)) + + // Newton step + x_new = x - error / beta_pdf + + // Ensure x stays in (0, 1) + x_new = clamp(x_new, 0.0001, 0.9999) + + if abs(x_new - x) < 1e-10: + return x_new + + x = x_new + +4. Return x (or error if not converged) + +Helper - logBeta function: +logBeta(a, b): + return log_gamma(a) + log_gamma(b) - log_gamma(a + b) +``` + +#### Asymptotic Expansion (Large a, b) + +``` +Algorithm: incompleteBetaAsymptotic(x, a, b) +Input: x ∈ [0, 1], a >> 1, b >> 1 +Output: I_x(a, b) + +// Use Normal approximation for large a, b +1. mu = a / (a + b) +2. sigma = sqrt(a * b / ((a + b)^2 * (a + b + 1))) + +3. z = (x - mu) / sigma + +4. return normalCDF(z) + +Alternative (more accurate): + Use Edgeworth expansion or saddlepoint approximation +``` + +### Complete Pseudocode + +```typescript +class IncompleteBeta { + private static readonly EPSILON = 1e-15 + private static readonly TINY = 1e-30 + private static readonly MAX_ITER = 200 + + static regularized(x: number, a: number, b: number): number { + if (x < 0 || x > 1) throw Error("x must be in [0, 1]") + if (a <= 0 || b <= 0) throw Error("a, b must be positive") + + if (x === 0) return 0 + if (x === 1) return 1 + + // Use symmetry + if (x > (a + 1) / (a + b + 2)) { + return 1 - this.regularized(1 - x, b, a) + } + + // Logarithmic prefix + const logBeta = logGamma(a) + logGamma(b) - logGamma(a + b) + const logPrefix = a * log(x) + b * log(1 - x) - logBeta - log(a) + + // Choose method + const cf = this.continuedFraction(x, a, b) + return exp(logPrefix) * cf + } + + private static continuedFraction(x: number, a: number, b: number): number { + const qab = a + b + const qap = a + 1 + const qam = a - 1 + + let c = 1.0 + let d = 1.0 - qab * x / qap + + if (abs(d) < this.TINY) d = this.TINY + d = 1.0 / d + let h = d + + for (let m = 1; m <= this.MAX_ITER; m++) { + const m2 = 2 * m + + // Even step + let aa = m * (b - m) * x / ((qam + m2) * (a + m2)) + d = 1.0 + aa * d + if (abs(d) < this.TINY) d = this.TINY + c = 1.0 + aa / c + if (abs(c) < this.TINY) c = this.TINY + d = 1.0 / d + h *= d * c + + // Odd step + aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2)) + d = 1.0 + aa * d + if (abs(d) < this.TINY) d = this.TINY + c = 1.0 + aa / c + if (abs(c) < this.TINY) c = this.TINY + d = 1.0 / d + const delta = d * c + h *= delta + + if (abs(delta - 1.0) < this.EPSILON) { + return h + } + } + + throw Error("Continued fraction did not converge") + } + + static inverse(p: number, a: number, b: number): number { + if (p < 0 || p > 1) throw Error("p must be in [0, 1]") + if (p === 0) return 0 + if (p === 1) return 1 + + // Initial guess + let x = this.initialGuess(p, a, b) + + // Newton-Raphson refinement + for (let iter = 0; iter < 100; iter++) { + const beta_cdf = this.regularized(x, a, b) + const error = beta_cdf - p + + const logBeta = logGamma(a) + logGamma(b) - logGamma(a + b) + const beta_pdf = exp((a - 1) * log(x) + (b - 1) * log(1 - x) - logBeta) + + const x_new = x - error / beta_pdf + const x_clamped = clamp(x_new, 0.0001, 0.9999) + + if (abs(x_clamped - x) < 1e-10) { + return x_clamped + } + + x = x_clamped + } + + return x + } + + private static initialGuess(p: number, a: number, b: number): number { + if (a >= 1 && b >= 1) { + // Wilson-Hilferty approximation + let pp: number, a1: number, b1: number + + if (p < 0.5) { + pp = p + a1 = a - 0.5 + b1 = b - 0.5 + } else { + pp = 1 - p + a1 = b - 0.5 + b1 = a - 0.5 + } + + const t = sqrt(-2 * log(pp)) + let x_init = (2.30753 + t * 0.27061) / (1 + t * (0.99229 + t * 0.04481)) + x_init = a1 / (a1 + b1 * exp(x_init)) + + return (p < 0.5) ? x_init : 1 - x_init + } else { + // Simple approximation + const lna = log(a / (a + b)) + const lnb = log(b / (a + b)) + const t = exp(a * lna) / a + const u = exp(b * lnb) / b + const w = t + u + + if (p < t / w) { + return pow(a * w * p, 1 / a) + } else { + return 1 - pow(b * w * (1 - p), 1 / b) + } + } + } +} +``` + +--- + +## Task 4.10: Multivariate Normal Distribution + +### Mathematical Formulas + +**Probability Density Function (PDF)**: +``` +f(x) = (2π)^(-k/2) * |Σ|^(-1/2) * exp[-1/2 * (x-μ)ᵀ Σ⁻¹ (x-μ)] + +where: +- x ∈ ℝᵏ (random vector) +- μ ∈ ℝᵏ (mean vector) +- Σ ∈ ℝᵏˣᵏ (covariance matrix, positive definite) +- k (dimension) +``` + +**Mahalanobis Distance**: +``` +D² = (x - μ)ᵀ Σ⁻¹ (x - μ) +``` + +### Algorithms + +#### PDF Computation + +``` +Algorithm: multivariateNormalPDF(x, mu, Sigma) +Input: x (k-vector), mu (k-vector), Sigma (k×k matrix) +Output: f(x) + +1. Validate: + - dim(x) = dim(mu) = k + - Sigma is k×k, symmetric, positive definite + +2. Compute Cholesky decomposition: + L = cholesky(Sigma) // Sigma = L * Lᵀ + + If decomposition fails: error "Sigma not positive definite" + +3. Compute determinant: + // det(Sigma) = (det(L))² = (Π L_ii)² + log_det_Sigma = 2 * Σ log(L_ii) + +4. Compute Mahalanobis distance: + diff = x - mu + + // Solve L * y = diff for y + y = solve_triangular_lower(L, diff) + + // D² = yᵀy + mahalanobis_sq = dot(y, y) + +5. Compute log PDF: + k = length(x) + log_pdf = -0.5 * (k * log(2*PI) + log_det_Sigma + mahalanobis_sq) + +6. Return exp(log_pdf) + +Optimization: +- Cache Cholesky decomposition if evaluating multiple x values +- Use log PDF directly when possible to avoid overflow +``` + +#### Random Sampling + +**Method**: Cholesky Factorization +``` +Algorithm: multivariateNormalRandom(mu, Sigma) +Input: mu (k-vector), Sigma (k×k covariance matrix) +Output: Random k-vector from N(μ, Σ) + +1. Validate Sigma (symmetric, positive definite) + +2. Compute Cholesky decomposition: + L = cholesky(Sigma) // Sigma = L * Lᵀ + +3. Generate standard normal vector: + z = [z₁, z₂, ..., z_k]ᵀ + where each z_i ~ N(0, 1) independently + +4. Transform: + x = μ + L * z + +5. Return x + +Explanation: +- If z ~ N(0, I), then L*z ~ N(0, L*Lᵀ) = N(0, Σ) +- Adding μ shifts to N(μ, Σ) +``` + +**Alternative Method**: Eigendecomposition +``` +Algorithm: multivariateNormalRandom_Eigen(mu, Sigma) +Input: mu, Sigma +Output: Random vector + +1. Eigendecomposition: + Sigma = Q * Λ * Qᵀ + where Q is orthogonal, Λ is diagonal with eigenvalues + +2. Generate standard normal: + z ~ N(0, I) + +3. Transform: + x = μ + Q * √Λ * z + +Note: Cholesky is faster; use eigen when Sigma is nearly singular +``` + +#### Conditional Distribution + +``` +Algorithm: multivariateNormalConditional(x1, mu, Sigma, indices) +Input: + - x1: observed values for subset of variables + - mu: full mean vector + - Sigma: full covariance matrix + - indices: which variables are observed +Output: Conditional mean and covariance + +// Partition: x = [x₁, x₂]ᵀ where x₁ is observed + +1. Partition mu: + mu1 = mu[indices] + mu2 = mu[not indices] + +2. Partition Sigma: + Sigma11 = Sigma[indices, indices] + Sigma12 = Sigma[indices, not indices] + Sigma21 = Sigma[not indices, indices] + Sigma22 = Sigma[not indices, not indices] + +3. Compute conditional distribution: + // x₂|x₁ ~ N(μ₂|₁, Σ₂|₁) + + Sigma11_inv = inverse(Sigma11) + + mu_conditional = mu2 + Sigma21 * Sigma11_inv * (x1 - mu1) + Sigma_conditional = Sigma22 - Sigma21 * Sigma11_inv * Sigma12 + +4. Return (mu_conditional, Sigma_conditional) +``` + +### Complete Pseudocode + +```typescript +class MultivariateNormalDistribution { + private k: integer // Dimension + private mu: Vector // Mean vector + private Sigma: Matrix // Covariance matrix + private L: Matrix // Cholesky decomposition (cached) + private logDetSigma: number // Log determinant (cached) + + constructor(mu: Vector, Sigma: Matrix) { + this.k = mu.length + + if (Sigma.rows !== this.k || Sigma.cols !== this.k) { + throw Error("Sigma dimensions must match mu") + } + + if (!isSymmetric(Sigma)) { + throw Error("Sigma must be symmetric") + } + + this.mu = mu.clone() + this.Sigma = Sigma.clone() + + // Precompute Cholesky decomposition + try { + this.L = choleskyDecomposition(Sigma) + } catch (e) { + throw Error("Sigma must be positive definite") + } + + // Compute log determinant + this.logDetSigma = 0 + for (let i = 0; i < this.k; i++) { + this.logDetSigma += 2 * log(this.L.get(i, i)) + } + } + + pdf(x: Vector): number { + if (x.length !== this.k) { + throw Error("x dimension must match distribution dimension") + } + + const logPDF = this.logPDF(x) + return exp(logPDF) + } + + logPDF(x: Vector): number { + // Compute (x - mu) + const diff = x.subtract(this.mu) + + // Solve L * y = diff + const y = solveTriangularLower(this.L, diff) + + // Mahalanobis distance squared: D² = yᵀy + const mahalanobisSq = y.dot(y) + + // Log PDF + const logPDF = -0.5 * (this.k * log(2 * PI) + + this.logDetSigma + + mahalanobisSq) + + return logPDF + } + + random(): Vector { + // Generate standard normal vector + const z = new Vector(this.k) + for (let i = 0; i < this.k; i++) { + z.set(i, randomNormal(0, 1)) + } + + // Transform: x = μ + L * z + const Lz = this.L.multiply(z) + return this.mu.add(Lz) + } + + randomMultiple(n: integer): Matrix { + // Generate n samples as columns of matrix + const samples = new Matrix(this.k, n) + + for (let j = 0; j < n; j++) { + const sample = this.random() + for (let i = 0; i < this.k; i++) { + samples.set(i, j, sample.get(i)) + } + } + + return samples + } + + mahalanobisDistance(x: Vector): number { + const diff = x.subtract(this.mu) + const y = solveTriangularLower(this.L, diff) + return sqrt(y.dot(y)) + } + + conditional(observed_indices: integer[], observed_values: Vector): { + mu: Vector, + Sigma: Matrix + } { + // Compute conditional distribution given some variables + const n_obs = observed_indices.length + const n_unobs = this.k - n_obs + + if (n_obs === 0 || n_obs === this.k) { + throw Error("Invalid conditioning") + } + + const unobserved_indices = Array.from({length: this.k}, (_, i) => i) + .filter(i => !observed_indices.includes(i)) + + // Partition mean + const mu1 = this.mu.slice(observed_indices) + const mu2 = this.mu.slice(unobserved_indices) + + // Partition covariance + const Sigma11 = this.Sigma.slice(observed_indices, observed_indices) + const Sigma12 = this.Sigma.slice(observed_indices, unobserved_indices) + const Sigma21 = this.Sigma.slice(unobserved_indices, observed_indices) + const Sigma22 = this.Sigma.slice(unobserved_indices, unobserved_indices) + + // Compute conditional parameters + const Sigma11_inv = Sigma11.inverse() + const diff_obs = observed_values.subtract(mu1) + + const mu_cond = mu2.add(Sigma21.multiply(Sigma11_inv).multiply(diff_obs)) + const Sigma_cond = Sigma22.subtract( + Sigma21.multiply(Sigma11_inv).multiply(Sigma12) + ) + + return { mu: mu_cond, Sigma: Sigma_cond } + } + + mean(): Vector { + return this.mu.clone() + } + + covariance(): Matrix { + return this.Sigma.clone() + } +} + +// Helper functions + +function choleskyDecomposition(A: Matrix): Matrix { + // Compute L such that A = L * Lᵀ + const n = A.rows + const L = new Matrix(n, n) + + for (let i = 0; i < n; i++) { + for (let j = 0; j <= i; j++) { + let sum = 0 + + for (let k = 0; k < j; k++) { + sum += L.get(i, k) * L.get(j, k) + } + + if (i === j) { + const diag = A.get(i, i) - sum + if (diag <= 0) { + throw Error("Matrix not positive definite") + } + L.set(i, j, sqrt(diag)) + } else { + L.set(i, j, (A.get(i, j) - sum) / L.get(j, j)) + } + } + } + + return L +} + +function solveTriangularLower(L: Matrix, b: Vector): Vector { + // Solve L * x = b for lower triangular L + const n = L.rows + const x = new Vector(n) + + for (let i = 0; i < n; i++) { + let sum = 0 + for (let j = 0; j < i; j++) { + sum += L.get(i, j) * x.get(j) + } + x.set(i, (b.get(i) - sum) / L.get(i, i)) + } + + return x +} +``` + +--- + +## Implementation Strategy + +### Phase 1: Core Discrete Distributions (Week 1-2) +1. Implement Poisson distribution (4.1) +2. Implement Binomial distribution (4.2) +3. Implement Geometric distribution (4.4) +4. Write comprehensive tests + +### Phase 2: Advanced Discrete Distributions (Week 3) +1. Implement Negative Binomial (4.3) +2. Implement Hypergeometric (4.5) +3. Cross-validate with statistical libraries + +### Phase 3: Additional Continuous Distributions (Week 4) +1. Implement Uniform continuous (4.6) +2. Implement Cauchy (4.7) +3. Implement Logistic (4.8) + +### Phase 4: Special Functions & Multivariate (Week 5-6) +1. Implement Incomplete Beta (4.9) + - Continued fraction + - Series expansion + - Inverse function +2. Implement Multivariate Normal (4.10) +3. Integration testing with Phase 3 distributions + +### Phase 5: WASM Optimization (Week 7-8) +1. Port algorithms to AssemblyScript +2. Benchmark and optimize +3. Implement parallel sampling for multivariate + +--- + +## Testing Requirements + +### Unit Tests +- PDF/PMF correctness (compare with known values) +- CDF correctness (boundary cases, monotonicity) +- Inverse CDF (round-trip: Q(F(x)) ≈ x) +- Random sampling (statistical tests: χ² goodness-of-fit, KS test) + +### Integration Tests +- Cross-distribution relationships (e.g., Geometric is special case of Negative Binomial) +- Incomplete Beta used in Binomial/Beta CDF +- Multivariate Normal marginals are univariate Normal + +### Performance Benchmarks +- PDF/CDF evaluation speed +- Random sampling throughput +- WASM vs JavaScript comparison +- Parallel sampling efficiency (multivariate) + +--- + +## Dependencies + +### From Phase 1 (Special Functions) +- `logGamma`: logarithm of gamma function +- `gamma`: gamma function +- `incompleteBeta`: regularized incomplete beta (task 4.9) + +### From Phase 3 (Continuous Distributions) +- `normalCDF`, `normalInverseCDF`: for approximations +- `gammaCDF`, `gammaRandom`: for Negative Binomial sampling + +### Internal Dependencies +- Random number generator (uniform) +- Matrix operations (Cholesky, solve triangular) +- Numerical constants (PI, EPSILON) + +--- + +## References + +1. **Poisson**: Ahrens & Dieter (1982) - "Computer Generation of Poisson Deviates" +2. **Binomial**: Kachitvichyanukul & Schmeiser (1988) - "Binomial Random Variate Generation" +3. **Negative Binomial**: Devroye (1986) - "Non-Uniform Random Variate Generation" +4. **Incomplete Beta**: DiDonato & Morris (1992) - "Algorithm 708" +5. **Multivariate Normal**: Gentle (2003) - "Random Number Generation and Monte Carlo Methods" + +--- + +## Success Criteria + +- ✅ All 10 distributions implemented with PDF/PMF, CDF, inverse CDF, and random sampling +- ✅ Numerical accuracy: relative error < 1e-12 for most inputs +- ✅ Random sampling passes KS test at α = 0.01 +- ✅ WASM achieves 3-8x speedup for large-scale operations +- ✅ Comprehensive documentation with examples +- ✅ 100% test coverage + +--- + +**End of Phase 4 Breakdown** diff --git a/PHASE_5_ROOT_FINDING_OPTIMIZATION.md b/PHASE_5_ROOT_FINDING_OPTIMIZATION.md new file mode 100644 index 0000000000..06e673204d --- /dev/null +++ b/PHASE_5_ROOT_FINDING_OPTIMIZATION.md @@ -0,0 +1,2912 @@ +# Phase 5: Root Finding & Optimization - Detailed Breakdown + +**Phase**: 5 of 8 +**Category**: Scientific Computing - Numerical Analysis +**Complexity**: High +**Estimated Effort**: 4-6 weeks +**Dependencies**: Phase 1 (Core Infrastructure), Phase 2 (Linear Algebra), Phase 4 (Numerical Integration) + +--- + +## Overview + +Phase 5 implements robust root-finding and optimization algorithms with automatic method selection, numerical differentiation, and sophisticated convergence strategies. These algorithms are essential for solving equations, minimizing functions, and finding equilibrium points in scientific and engineering applications. + +### Performance Goals +- **Root finding**: 2-5x faster than pure JavaScript (via WASM) +- **Optimization**: 3-8x faster for gradient-based methods +- **System solving**: 5-15x faster for large nonlinear systems +- **Memory efficiency**: Minimize function evaluations through intelligent caching + +### Key Innovations +1. Hybrid algorithms combining multiple strategies +2. Automatic numerical differentiation with error estimation +3. Intelligent bracket detection for root finding +4. Adaptive line search with backtracking +5. BFGS Hessian approximation for quasi-Newton methods + +--- + +## Task 5.1: Brent's Method (Root Finding) + +### Overview + +Brent's method is a hybrid root-finding algorithm that combines the reliability of bisection with the speed of the secant method and inverse quadratic interpolation. It's considered one of the best general-purpose root-finding algorithms. + +### Mathematical Foundation + +**Problem**: Find x such that f(x) = 0 + +**Requirements**: +- Function f must be continuous on [a, b] +- f(a) and f(b) must have opposite signs: f(a) · f(b) < 0 +- Guarantees convergence to a root + +**Convergence Rate**: +- **Superlinear**: Order ~1.618 (golden ratio) in best case +- **Linear**: Falls back to bisection if necessary +- **Theorem**: Brent's method never requires more function evaluations than bisection + +### Algorithm Components + +#### 1. Inverse Quadratic Interpolation (IQI) + +When we have three points (a, fa), (b, fb), (c, fc), fit inverse quadratic through points and solve for f(x) = 0: + +``` +x = (fa·fb / ((fc-fa)·(fc-fb))) · c + + (fa·fc / ((fb-fa)·(fb-fc))) · b + + (fb·fc / ((fa-fb)·(fa-fc))) · a +``` + +**When to use**: When points are distinct and not collinear + +#### 2. Secant Method + +When IQI is not applicable, use linear interpolation: + +``` +x = b - fb · (b - a) / (fb - fa) +``` + +**When to use**: When only two points available or IQI unstable + +#### 3. Bisection Method + +Fallback when other methods produce unreliable results: + +``` +x = (a + b) / 2 +``` + +**When to use**: When interpolation step is too small or outside bracket + +### State Machine Pseudocode + +``` +State: INIT, IQI_TRY, SECANT_TRY, BISECT, CONVERGED, MAX_ITER + +function brent_state_machine(f, a, b, tol, max_iter): + // Initialization + state = INIT + fa = f(a), fb = f(b) + + if sign(fa) == sign(fb): + error("Root not bracketed") + + // Ensure |f(a)| ≥ |f(b)| + if |fa| < |fb|: + swap(a, b) + swap(fa, fb) + + c = a, fc = fa + mflag = true // Method flag: true = used bisection last time + + iter = 0 + + while state != CONVERGED and state != MAX_ITER: + switch state: + case INIT: + state = IQI_TRY + + case IQI_TRY: + // Check if we can use IQI + if fa != fc and fb != fc: + // Try inverse quadratic interpolation + s = compute_iqi(a, b, c, fa, fb, fc) + state = CHECK_ACCEPTANCE + else: + state = SECANT_TRY + + case SECANT_TRY: + // Use secant method + s = b - fb * (b - a) / (fb - fa) + state = CHECK_ACCEPTANCE + + case CHECK_ACCEPTANCE: + // Acceptance criteria for s + accept = true + + // Condition 1: s must be between (3a+b)/4 and b + bound = (3*a + b) / 4 + if not (s between min(bound, b) and max(bound, b)): + accept = false + + // Condition 2: If bisection was used last time + if mflag and |s - b| >= |b - c| / 2: + accept = false + + // Condition 3: If bisection was not used last time + if not mflag and |s - b| >= |c - d| / 2: + accept = false + + // Condition 4: If bisection was used last time + if mflag and |b - c| < tol: + accept = false + + // Condition 5: If bisection was not used last time + if not mflag and |c - d| < tol: + accept = false + + if accept: + mflag = false + state = UPDATE_BRACKET + else: + state = BISECT + + case BISECT: + s = (a + b) / 2 + mflag = true + state = UPDATE_BRACKET + + case UPDATE_BRACKET: + fs = f(s) + d = c // Save for condition checking + c = b, fc = fb + + // Update bracket + if sign(fa) == sign(fs): + a = s, fa = fs + else: + b = s, fb = fs + + // Ensure |f(a)| ≥ |f(b)| + if |fa| < |fb|: + swap(a, b) + swap(fa, fb) + + // Check convergence + iter++ + if |fb| < tol or |b - a| < tol: + state = CONVERGED + else if iter >= max_iter: + state = MAX_ITER + else: + state = IQI_TRY + + if state == CONVERGED: + return b, fb, iter, "success" + else: + return b, fb, iter, "max_iterations_reached" +``` + +### Complete Implementation Pseudocode + +```typescript +function brent( + f: (x: number) => number, + a: number, + b: number, + options: { + tol?: number = 1e-10, + maxIter?: number = 100, + relTol?: number = 2.220446049250313e-16 // machine epsilon + } +): { root: number, fval: number, iterations: number, status: string } { + + const { tol, maxIter, relTol } = options + + // Step 1: Initial evaluation + let fa = f(a) + let fb = f(b) + + // Step 2: Check bracketing + if (sign(fa) === sign(fb)) { + throw new Error(`Root not bracketed: f(${a}) = ${fa}, f(${b}) = ${fb}`) + } + + // Step 3: Ensure |f(a)| ≥ |f(b)| + if (Math.abs(fa) < Math.abs(fb)) { + [a, b] = [b, a] + [fa, fb] = [fb, fa] + } + + // Step 4: Initialize + let c = a + let fc = fa + let d = c // Previous iteration's c value + let mflag = true // Method flag + let s: number // New estimate + let fs: number + + let iter = 0 + + // Step 5: Main iteration loop + while (iter < maxIter) { + // Convergence check + const converged = Math.abs(fb) < tol || Math.abs(b - a) < tol + + if (converged) { + return { + root: b, + fval: fb, + iterations: iter, + status: 'success' + } + } + + // Step 6: Choose method + if (fa !== fc && fb !== fc) { + // Inverse quadratic interpolation + const L0 = (a * fb * fc) / ((fa - fb) * (fa - fc)) + const L1 = (b * fa * fc) / ((fb - fa) * (fb - fc)) + const L2 = (c * fa * fb) / ((fc - fa) * (fc - fb)) + s = L0 + L1 + L2 + } else { + // Secant method + s = b - fb * (b - a) / (fb - fa) + } + + // Step 7: Check acceptance criteria + const bound = (3 * a + b) / 4 + const minBound = Math.min(bound, b) + const maxBound = Math.max(bound, b) + + const condition1 = !(s > minBound && s < maxBound) + const condition2 = mflag && Math.abs(s - b) >= Math.abs(b - c) / 2 + const condition3 = !mflag && Math.abs(s - b) >= Math.abs(c - d) / 2 + const condition4 = mflag && Math.abs(b - c) < tol + const condition5 = !mflag && Math.abs(c - d) < tol + + if (condition1 || condition2 || condition3 || condition4 || condition5) { + // Use bisection + s = (a + b) / 2 + mflag = true + } else { + mflag = false + } + + // Step 8: Evaluate at new point + fs = f(s) + + // Step 9: Update + d = c + c = b + fc = fb + + // Step 10: Update bracket + if (sign(fa) === sign(fs)) { + a = s + fa = fs + } else { + b = s + fb = fs + } + + // Step 11: Ensure |f(a)| ≥ |f(b)| + if (Math.abs(fa) < Math.abs(fb)) { + [a, b] = [b, a] + [fa, fb] = [fb, fa] + } + + iter++ + } + + // Maximum iterations reached + return { + root: b, + fval: fb, + iterations: iter, + status: 'max_iterations_reached' + } +} + +// Helper function +function sign(x: number): number { + return x >= 0 ? 1 : -1 +} +``` + +### Edge Cases and Error Handling + +```typescript +// Edge case 1: Exact root at boundary +if (fa === 0) return { root: a, fval: 0, iterations: 0, status: 'success' } +if (fb === 0) return { root: b, fval: 0, iterations: 0, status: 'success' } + +// Edge case 2: Very narrow bracket +if (Math.abs(b - a) < tol) { + return { root: (a + b) / 2, fval: f((a + b) / 2), iterations: 0, status: 'success' } +} + +// Edge case 3: NaN or Inf values +if (!isFinite(fa) || !isFinite(fb)) { + throw new Error('Function returned non-finite value') +} + +// Edge case 4: Flat function (both values very close to zero) +if (Math.abs(fa) < tol && Math.abs(fb) < tol) { + return { root: (a + b) / 2, fval: 0, iterations: 0, status: 'success' } +} +``` + +### Convergence Analysis + +**Theorem 1** (Brent, 1973): If f is continuous on [a, b] and f(a)·f(b) < 0, then Brent's method: +1. Always converges to a root +2. Never requires more evaluations than bisection +3. Usually requires fewer evaluations than bisection + +**Convergence Order**: +- Best case: ~1.618 (superlinear, golden ratio) +- Average case: ~1.3-1.5 +- Worst case: 1 (linear, same as bisection) + +**Error Bound**: After n iterations with initial bracket [a₀, b₀]: +``` +|xₙ - x*| ≤ (b₀ - a₀) / 2ⁿ +``` + +--- + +## Task 5.2: Newton-Raphson Method + +### Overview + +Newton-Raphson is a powerful root-finding method that uses the derivative to achieve quadratic convergence near the root. We implement both analytical and numerical derivative versions, plus line search for robustness. + +### Mathematical Foundation + +**Basic Algorithm**: +``` +xₙ₊₁ = xₙ - f(xₙ) / f'(xₙ) +``` + +**Geometric Interpretation**: At each iteration, we: +1. Compute the tangent line at (xₙ, f(xₙ)) +2. Find where the tangent intersects the x-axis +3. Use that intersection as the next estimate + +**Convergence Theorem**: If f ∈ C²[a,b], f(x*) = 0, f'(x*) ≠ 0, and x₀ is sufficiently close to x*, then Newton-Raphson converges quadratically: +``` +|eₙ₊₁| ≈ M|eₙ|² where M = |f''(x*)| / (2|f'(x*)|) +``` + +### Automatic Numerical Differentiation + +When analytical derivative is not available: + +**Central Difference Formula** (4th order accurate): +``` +f'(x) ≈ (-f(x+2h) + 8f(x+h) - 8f(x-h) + f(x-2h)) / (12h) +``` + +**Optimal Step Size**: +``` +h = ∛(3ε|x|) where ε = machine epsilon ≈ 2.22e-16 +``` + +For x = 0, use h = ∛(3ε) + +**Error Estimation**: +``` +Error ≈ h⁴|f⁽⁵⁾(x)| / 30 +``` + +### Line Search Algorithm + +To ensure global convergence, we use backtracking line search with Armijo condition: + +``` +xₙ₊₁ = xₙ - α · (f(xₙ) / f'(xₙ)) +``` + +where α ∈ (0, 1] satisfies: +``` +|f(xₙ - α·δ)| ≤ |f(xₙ)| - c₁·α·δ·f'(xₙ) +``` + +Typically c₁ = 1e-4. + +### Complete Implementation Pseudocode + +```typescript +interface NewtonOptions { + tol?: number // Absolute tolerance (default: 1e-10) + relTol?: number // Relative tolerance (default: 1e-10) + maxIter?: number // Maximum iterations (default: 100) + derivative?: (x: number) => number // Analytical derivative + useLineSearch?: boolean // Enable line search (default: true) + dampingFactor?: number // Initial damping (default: 1.0) +} + +function newton( + f: (x: number) => number, + x0: number, + options: NewtonOptions = {} +): { root: number, fval: number, iterations: number, status: string } { + + const { + tol = 1e-10, + relTol = 1e-10, + maxIter = 100, + derivative, + useLineSearch = true, + dampingFactor = 1.0 + } = options + + const eps = 2.220446049250313e-16 // Machine epsilon + + // Step 1: Initialize + let x = x0 + let fx = f(x) + let iter = 0 + + // Check if already at root + if (Math.abs(fx) < tol) { + return { root: x, fval: fx, iterations: 0, status: 'success' } + } + + // Derivative function (analytical or numerical) + const fprime = derivative || ((x: number) => numericalDerivative(f, x, eps)) + + // Step 2: Main iteration loop + while (iter < maxIter) { + // Compute derivative + const dfx = fprime(x) + + // Check for zero derivative + if (Math.abs(dfx) < eps) { + return { + root: x, + fval: fx, + iterations: iter, + status: 'derivative_zero' + } + } + + // Compute Newton step + const delta = fx / dfx + + // Line search + let alpha = dampingFactor + let xNew: number + let fxNew: number + + if (useLineSearch) { + // Backtracking line search + const c1 = 1e-4 // Armijo constant + const rho = 0.5 // Backtracking factor + const maxLineSearchIter = 20 + + let lsIter = 0 + let accepted = false + + while (lsIter < maxLineSearchIter && !accepted) { + xNew = x - alpha * delta + fxNew = f(xNew) + + // Armijo condition (sufficient decrease) + if (Math.abs(fxNew) <= Math.abs(fx) - c1 * alpha * Math.abs(delta * dfx)) { + accepted = true + } else { + alpha *= rho + lsIter++ + } + } + + if (!accepted) { + // Line search failed, use bisection-like step + alpha = 0.5 + xNew = x - alpha * delta + fxNew = f(xNew) + } + } else { + // No line search + xNew = x - alpha * delta + fxNew = f(xNew) + } + + // Convergence checks + const absChange = Math.abs(xNew - x) + const relChange = absChange / (Math.abs(x) + eps) + const absValue = Math.abs(fxNew) + + iter++ + + // Update for next iteration + x = xNew + fx = fxNew + + // Check convergence + if (absValue < tol) { + return { root: x, fval: fx, iterations: iter, status: 'success' } + } + + if (absChange < tol || relChange < relTol) { + return { root: x, fval: fx, iterations: iter, status: 'success' } + } + + // Detect divergence + if (!isFinite(x) || !isFinite(fx)) { + return { root: x, fval: fx, iterations: iter, status: 'diverged' } + } + } + + // Maximum iterations reached + return { + root: x, + fval: fx, + iterations: iter, + status: 'max_iterations_reached' + } +} + +// Numerical derivative using central difference +function numericalDerivative( + f: (x: number) => number, + x: number, + eps: number +): number { + // Optimal step size for central difference + const h = Math.cbrt(3 * eps * Math.max(Math.abs(x), 1.0)) + + // 4th order central difference + const fp2h = f(x + 2 * h) + const fp1h = f(x + h) + const fm1h = f(x - h) + const fm2h = f(x - 2 * h) + + const derivative = (-fp2h + 8 * fp1h - 8 * fm1h + fm2h) / (12 * h) + + return derivative +} +``` + +### Safeguards and Edge Cases + +```typescript +// Safeguard 1: Derivative too small +if (Math.abs(dfx) < sqrt(eps)) { + // Switch to secant method or bisection + return fallbackToSecant(f, x, fx, options) +} + +// Safeguard 2: Step too large +const maxStep = 100 * Math.max(Math.abs(x), 1.0) +if (Math.abs(delta) > maxStep) { + delta = Math.sign(delta) * maxStep +} + +// Safeguard 3: Oscillation detection +const history = [] // Keep last 5 iterates +if (detectOscillation(history)) { + return fallbackToBisection(f, history, options) +} + +// Safeguard 4: Slow convergence +if (iter > 10 && Math.abs(fxNew) > 0.9 * Math.abs(fx)) { + // Not making progress, enable/strengthen line search + dampingFactor *= 0.5 +} +``` + +### Convergence Analysis + +**Quadratic Convergence** (near root): +``` +eₙ₊₁ ≈ (f''(x*) / (2f'(x*))) · eₙ² +``` + +Number of correct digits approximately doubles each iteration. + +**Basin of Attraction**: Region around root where Newton converges depends on: +``` +|x₀ - x*| < 2|f'(x*)| / ||f''|| +``` + +**Failure Modes**: +1. Zero derivative: f'(x) = 0 +2. Poor initial guess: divergence or convergence to wrong root +3. Flat regions: slow convergence +4. Discontinuous derivative: unpredictable behavior + +--- + +## Task 5.3: fzero (General Root Finder) + +### Overview + +`fzero` is a robust general-purpose root finder that: +1. Automatically detects brackets when single point given +2. Selects optimal method (Brent's or Newton's) based on situation +3. Handles multiple roots and provides diagnostic information + +### Bracket Detection Algorithm + +When user provides single starting point x₀, we need to find [a, b] such that f(a)·f(b) < 0. + +**Strategy**: Exponential search outward from x₀ + +``` +Step 1: Evaluate f(x₀) +Step 2: Search in both directions with exponentially increasing steps +Step 3: Stop when bracket found or maximum search radius reached +``` + +### Bracket Detection Pseudocode + +```typescript +function findBracket( + f: (x: number) => number, + x0: number, + options: { maxRadius?: number, maxIter?: number } +): { a: number, b: number, fa: number, fb: number } | null { + + const maxRadius = options.maxRadius || 1e6 + const maxIter = options.maxIter || 50 + + // Initial evaluation + const f0 = f(x0) + + if (Math.abs(f0) < 1e-14) { + // Already at root + return { a: x0, b: x0, fa: f0, fb: f0 } + } + + // Initial step size + let step = Math.max(0.1 * Math.abs(x0), 0.1) + + // Search in both directions + let iter = 0 + + while (iter < maxIter && step < maxRadius) { + // Try positive direction + const xp = x0 + step + const fp = f(xp) + + if (sign(f0) !== sign(fp)) { + return { a: x0, b: xp, fa: f0, fb: fp } + } + + // Try negative direction + const xn = x0 - step + const fn = f(xn) + + if (sign(f0) !== sign(fn)) { + return { a: xn, b: x0, fa: fn, fb: f0 } + } + + // Also check if we found bracket on opposite side + if (sign(fp) !== sign(fn)) { + return { a: xn, b: xp, fa: fn, fb: fp } + } + + // Increase step size exponentially + step *= 2 + iter++ + } + + // No bracket found + return null +} + +function sign(x: number): number { + return x >= 0 ? 1 : -1 +} +``` + +### Method Selection Logic + +```typescript +enum RootFindingMethod { + BRENT, // Bracketing method (reliable) + NEWTON, // Derivative-based (fast) + SECANT, // Derivative-free (moderate speed) + BISECTION // Fallback (always works if bracketed) +} + +function selectMethod( + hasBracket: boolean, + hasDerivative: boolean, + tolerance: number +): RootFindingMethod { + + if (!hasBracket && !hasDerivative) { + // Need to find bracket first + return RootFindingMethod.BRENT + } + + if (hasDerivative) { + // Use Newton if derivative available and we're close enough + return RootFindingMethod.NEWTON + } + + if (hasBracket) { + // Use Brent's method for bracketed problem without derivative + return RootFindingMethod.BRENT + } + + // Default: secant method + return RootFindingMethod.SECANT +} +``` + +### Complete fzero Implementation + +```typescript +interface FzeroOptions { + tol?: number + maxIter?: number + derivative?: (x: number) => number + method?: 'auto' | 'brent' | 'newton' | 'secant' | 'bisection' + display?: boolean // Show iteration progress +} + +interface FzeroResult { + root: number + fval: number + iterations: number + method: string + status: string + bracket?: [number, number] +} + +function fzero( + f: (x: number) => number, + x0: number | [number, number], + options: FzeroOptions = {} +): FzeroResult { + + const { + tol = 1e-10, + maxIter = 100, + derivative, + method = 'auto', + display = false + } = options + + // Step 1: Determine if we have a bracket + let bracket: { a: number, b: number, fa: number, fb: number } | null = null + let hasBracket = false + + if (Array.isArray(x0)) { + // User provided bracket + const [a, b] = x0 + const fa = f(a) + const fb = f(b) + + if (sign(fa) !== sign(fb)) { + bracket = { a, b, fa, fb } + hasBracket = true + } else { + // Not a valid bracket, try to expand + bracket = findBracket(f, (a + b) / 2, { maxRadius: Math.abs(b - a) * 10 }) + hasBracket = bracket !== null + } + } else { + // Single starting point - try to find bracket + bracket = findBracket(f, x0, {}) + hasBracket = bracket !== null + + if (!hasBracket && !derivative) { + throw new Error( + 'Could not find bracket and no derivative provided. ' + + 'Please provide either a valid bracket [a,b] or a derivative function.' + ) + } + } + + // Step 2: Select method + let selectedMethod: string + + if (method === 'auto') { + if (hasBracket && !derivative) { + selectedMethod = 'brent' + } else if (derivative) { + selectedMethod = 'newton' + } else { + selectedMethod = 'secant' + } + } else { + selectedMethod = method + } + + // Validate method compatibility + if (selectedMethod === 'brent' && !hasBracket) { + throw new Error('Brent method requires a bracket') + } + + if (selectedMethod === 'newton' && !derivative && !hasBracket) { + throw new Error('Newton method requires derivative or bracket for numerical differentiation') + } + + // Step 3: Call appropriate solver + let result: any + + switch (selectedMethod) { + case 'brent': + if (!bracket) throw new Error('Bracket required for Brent method') + result = brent(f, bracket.a, bracket.b, { tol, maxIter }) + result.bracket = [bracket.a, bracket.b] + break + + case 'newton': + const startX = hasBracket ? (bracket!.a + bracket!.b) / 2 : (x0 as number) + result = newton(f, startX, { + tol, + maxIter, + derivative, + useLineSearch: true + }) + if (hasBracket) { + result.bracket = [bracket!.a, bracket!.b] + } + break + + case 'secant': + const x0Sec = hasBracket ? bracket!.a : (x0 as number) + const x1Sec = hasBracket ? bracket!.b : (x0 as number) * 1.1 + 0.1 + result = secant(f, x0Sec, x1Sec, { tol, maxIter }) + break + + case 'bisection': + if (!bracket) throw new Error('Bracket required for bisection') + result = bisection(f, bracket.a, bracket.b, { tol, maxIter }) + result.bracket = [bracket.a, bracket.b] + break + + default: + throw new Error(`Unknown method: ${selectedMethod}`) + } + + // Step 4: Add method info to result + result.method = selectedMethod + + // Step 5: Display if requested + if (display) { + console.log(`fzero: ${selectedMethod} method`) + console.log(` Root: ${result.root}`) + console.log(` f(root): ${result.fval}`) + console.log(` Iterations: ${result.iterations}`) + console.log(` Status: ${result.status}`) + if (result.bracket) { + console.log(` Bracket: [${result.bracket[0]}, ${result.bracket[1]}]`) + } + } + + return result +} + +// Secant method implementation +function secant( + f: (x: number) => number, + x0: number, + x1: number, + options: { tol: number, maxIter: number } +): { root: number, fval: number, iterations: number, status: string } { + + const { tol, maxIter } = options + + let xPrev = x0 + let x = x1 + let fPrev = f(xPrev) + let fx = f(x) + + for (let iter = 0; iter < maxIter; iter++) { + // Check convergence + if (Math.abs(fx) < tol) { + return { root: x, fval: fx, iterations: iter, status: 'success' } + } + + // Secant update + const denominator = fx - fPrev + + if (Math.abs(denominator) < 1e-14) { + return { root: x, fval: fx, iterations: iter, status: 'derivative_zero' } + } + + const xNew = x - fx * (x - xPrev) / denominator + + // Update for next iteration + xPrev = x + fPrev = fx + x = xNew + fx = f(x) + } + + return { root: x, fval: fx, iterations: maxIter, status: 'max_iterations_reached' } +} + +// Bisection method implementation +function bisection( + f: (x: number) => number, + a: number, + b: number, + options: { tol: number, maxIter: number } +): { root: number, fval: number, iterations: number, status: string } { + + const { tol, maxIter } = options + + let fa = f(a) + let fb = f(b) + + if (sign(fa) === sign(fb)) { + throw new Error('Bisection requires f(a) and f(b) to have opposite signs') + } + + for (let iter = 0; iter < maxIter; iter++) { + const c = (a + b) / 2 + const fc = f(c) + + if (Math.abs(fc) < tol || Math.abs(b - a) < tol) { + return { root: c, fval: fc, iterations: iter, status: 'success' } + } + + if (sign(fa) === sign(fc)) { + a = c + fa = fc + } else { + b = c + fb = fc + } + } + + const root = (a + b) / 2 + return { root, fval: f(root), iterations: maxIter, status: 'max_iterations_reached' } +} +``` + +### Multiple Root Handling + +```typescript +function fzeroMultiple( + f: (x: number) => number, + searchInterval: [number, number], + options: FzeroOptions & { numRoots?: number } = {} +): FzeroResult[] { + + const { numRoots = Infinity } = options + const [a, b] = searchInterval + const roots: FzeroResult[] = [] + + // Step 1: Sample function to detect sign changes + const numSamples = 1000 + const dx = (b - a) / numSamples + const samples: { x: number, fx: number }[] = [] + + for (let i = 0; i <= numSamples; i++) { + const x = a + i * dx + samples.push({ x, fx: f(x) }) + } + + // Step 2: Find all brackets + const brackets: [number, number][] = [] + + for (let i = 0; i < samples.length - 1; i++) { + if (sign(samples[i].fx) !== sign(samples[i + 1].fx)) { + brackets.push([samples[i].x, samples[i + 1].x]) + } + } + + // Step 3: Refine each bracket + for (const bracket of brackets) { + if (roots.length >= numRoots) break + + try { + const result = fzero(f, bracket, options) + + // Check if this is a new root (not duplicate) + const isDuplicate = roots.some(r => Math.abs(r.root - result.root) < options.tol!) + + if (!isDuplicate) { + roots.push(result) + } + } catch (e) { + // Skip this bracket if solving fails + continue + } + } + + return roots +} +``` + +--- + +## Task 5.4: fsolve (Nonlinear System Solver) + +### Overview + +`fsolve` solves systems of nonlinear equations: +``` +F(x) = 0 where F: ℝⁿ → ℝⁿ +``` + +Implements: +1. Multivariate Newton-Raphson with Jacobian +2. Broyden's method for Jacobian-free solving +3. Line search for global convergence +4. Trust region methods + +### Multivariate Newton-Raphson Algorithm + +**Update Formula**: +``` +x_{k+1} = x_k - J(x_k)⁻¹ F(x_k) +``` + +where J(x) is the Jacobian matrix: +``` +J_{ij} = ∂F_i / ∂x_j +``` + +**Practical Implementation**: Solve linear system instead of inverting: +``` +J(x_k) · Δx = -F(x_k) +x_{k+1} = x_k + Δx +``` + +### Jacobian Computation + +#### Analytical Jacobian + +```typescript +type JacobianFunction = (x: number[]) => number[][] + +// User provides analytical Jacobian +const jacobian: JacobianFunction = (x) => [ + [∂F₁/∂x₁, ∂F₁/∂x₂, ..., ∂F₁/∂xₙ], + [∂F₂/∂x₁, ∂F₂/∂x₂, ..., ∂F₂/∂xₙ], + ... + [∂Fₙ/∂x₁, ∂Fₙ/∂x₂, ..., ∂Fₙ/∂xₙ] +] +``` + +#### Numerical Jacobian (Forward Difference) + +```typescript +function numericalJacobian( + F: (x: number[]) => number[], + x: number[], + eps: number = 2.22e-16 +): number[][] { + + const n = x.length + const F0 = F(x) + const J: number[][] = Array(n).fill(0).map(() => Array(n).fill(0)) + + for (let j = 0; j < n; j++) { + // Optimal step size for j-th variable + const h = Math.sqrt(eps) * Math.max(Math.abs(x[j]), 1.0) + + // Perturb j-th component + const xPerturbed = [...x] + xPerturbed[j] += h + + const FPerturbed = F(xPerturbed) + + // Compute j-th column of Jacobian + for (let i = 0; i < n; i++) { + J[i][j] = (FPerturbed[i] - F0[i]) / h + } + } + + return J +} +``` + +#### Numerical Jacobian (Central Difference - More Accurate) + +```typescript +function numericalJacobianCentral( + F: (x: number[]) => number[], + x: number[], + eps: number = 2.22e-16 +): number[][] { + + const n = x.length + const J: number[][] = Array(n).fill(0).map(() => Array(n).fill(0)) + + for (let j = 0; j < n; j++) { + // Optimal step size for central difference + const h = Math.cbrt(eps) * Math.max(Math.abs(x[j]), 1.0) + + // Forward and backward perturbations + const xForward = [...x] + const xBackward = [...x] + xForward[j] += h + xBackward[j] -= h + + const FForward = F(xForward) + const FBackward = F(xBackward) + + // Compute j-th column using central difference + for (let i = 0; i < n; i++) { + J[i][j] = (FForward[i] - FBackward[i]) / (2 * h) + } + } + + return J +} +``` + +### Broyden's Method + +**Idea**: Update Jacobian approximation instead of recomputing: + +``` +J_{k+1} = J_k + ((F(x_{k+1}) - F(x_k) - J_k·Δx) ⊗ Δx^T) / (Δx^T·Δx) +``` + +where ⊗ denotes outer product. + +**Good Broyden Update** (rank-1): +``` +B_{k+1} = B_k + ((Δx - B_k·ΔF) ⊗ ΔF^T) / (ΔF^T·ΔF) +``` + +where B ≈ J⁻¹, ΔF = F(x_{k+1}) - F(x_k) + +### Line Search for Nonlinear Systems + +**Merit Function**: Use L2 norm as scalar measure of progress: +``` +φ(x) = ½||F(x)||₂² +``` + +**Armijo Condition**: +``` +φ(x_k + α·Δx) ≤ φ(x_k) + c₁·α·∇φ(x_k)^T·Δx +``` + +where ∇φ(x) = J(x)^T·F(x) + +### Complete fsolve Implementation + +```typescript +interface FsolveOptions { + tol?: number // Function tolerance + xTol?: number // Solution tolerance + maxIter?: number // Maximum iterations + jacobian?: (x: number[]) => number[][] // Analytical Jacobian + method?: 'newton' | 'broyden' // Solution method + useLineSearch?: boolean // Enable line search + display?: boolean // Show progress +} + +interface FsolveResult { + solution: number[] + fval: number[] // F(solution) + norm: number // ||F(solution)|| + iterations: number + jacobianEvals: number // Number of Jacobian evaluations + functionEvals: number // Number of function evaluations + status: string +} + +function fsolve( + F: (x: number[]) => number[], + x0: number[], + options: FsolveOptions = {} +): FsolveResult { + + const { + tol = 1e-10, + xTol = 1e-10, + maxIter = 100, + jacobian, + method = 'newton', + useLineSearch = true, + display = false + } = options + + const n = x0.length + let x = [...x0] + let Fx = F(x) + let normFx = norm2(Fx) + + let iter = 0 + let jacEvals = 0 + let funcEvals = 1 + + // For Broyden's method + let B: number[][] | null = null // Approximate inverse Jacobian + + // Initial Jacobian (for both methods) + let J = jacobian ? jacobian(x) : numericalJacobianCentral(F, x) + jacEvals++ + + while (iter < maxIter) { + // Convergence check + if (normFx < tol) { + return { + solution: x, + fval: Fx, + norm: normFx, + iterations: iter, + jacobianEvals: jacEvals, + functionEvals: funcEvals, + status: 'success' + } + } + + // Compute search direction + let dx: number[] + + if (method === 'newton' || iter === 0) { + // Solve J·dx = -F for Newton step + dx = solveLU(J, scalarMultiply(Fx, -1)) + } else if (method === 'broyden') { + // Use Broyden update + if (B === null) { + // Initialize B ≈ J⁻¹ + B = invertMatrix(J) + } + + // dx = -B·F + dx = matrixVectorMultiply(B, scalarMultiply(Fx, -1)) + } else { + throw new Error(`Unknown method: ${method}`) + } + + // Line search + let alpha = 1.0 + let xNew: number[] + let FxNew: number[] + let normFxNew: number + + if (useLineSearch) { + const c1 = 1e-4 + const rho = 0.5 + const maxLsIter = 20 + + const phi0 = 0.5 * normFx * normFx + const Jdx = matrixVectorMultiply(J, dx) + const gradPhi0_dot_dx = dotProduct(Fx, Jdx) + + let lsIter = 0 + let accepted = false + + while (lsIter < maxLsIter && !accepted) { + xNew = vectorAdd(x, scalarMultiply(dx, alpha)) + FxNew = F(xNew) + funcEvals++ + normFxNew = norm2(FxNew) + + const phi = 0.5 * normFxNew * normFxNew + + // Armijo condition + if (phi <= phi0 + c1 * alpha * gradPhi0_dot_dx) { + accepted = true + } else { + alpha *= rho + lsIter++ + } + } + + if (!accepted) { + // Line search failed, take small step + alpha = 0.01 + xNew = vectorAdd(x, scalarMultiply(dx, alpha)) + FxNew = F(xNew) + funcEvals++ + normFxNew = norm2(FxNew) + } + } else { + xNew = vectorAdd(x, dx) + FxNew = F(xNew) + funcEvals++ + normFxNew = norm2(FxNew) + } + + // Update Jacobian or Broyden approximation + if (method === 'broyden' && iter > 0 && B !== null) { + // Broyden update: B_{k+1} = B_k + ((Δx - B_k·ΔF) ⊗ ΔF^T) / (ΔF^T·ΔF) + const deltaF = vectorSubtract(FxNew, Fx) + const deltaX = vectorSubtract(xNew, x) + + const BdeltaF = matrixVectorMultiply(B, deltaF) + const numerator = vectorSubtract(deltaX, BdeltaF) + const denominator = dotProduct(deltaF, deltaF) + + if (Math.abs(denominator) > 1e-14) { + // Rank-1 update + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + B[i][j] += (numerator[i] * deltaF[j]) / denominator + } + } + } + } else if (method === 'newton') { + // Recompute Jacobian every iteration (or every few iterations) + const recomputeFreq = Math.min(n, 5) + if (iter % recomputeFreq === 0) { + J = jacobian ? jacobian(xNew) : numericalJacobianCentral(F, xNew) + jacEvals++ + } + } + + // Check step size convergence + const stepNorm = norm2(vectorSubtract(xNew, x)) + const relStepNorm = stepNorm / (norm2(x) + 1e-14) + + // Update for next iteration + x = xNew + Fx = FxNew + normFx = normFxNew + iter++ + + if (display) { + console.log(`Iteration ${iter}: ||F|| = ${normFx.toExponential(4)}, step = ${stepNorm.toExponential(4)}`) + } + + // Check step convergence + if (stepNorm < xTol || relStepNorm < xTol) { + return { + solution: x, + fval: Fx, + norm: normFx, + iterations: iter, + jacobianEvals: jacEvals, + functionEvals: funcEvals, + status: normFx < tol ? 'success' : 'step_too_small' + } + } + + // Detect stagnation + if (normFxNew > 0.99 * normFx && iter > 10) { + return { + solution: x, + fval: Fx, + norm: normFx, + iterations: iter, + jacobianEvals: jacEvals, + functionEvals: funcEvals, + status: 'stagnated' + } + } + } + + return { + solution: x, + fval: Fx, + norm: normFx, + iterations: iter, + jacobianEvals: jacEvals, + functionEvals: funcEvals, + status: 'max_iterations_reached' + } +} + +// Helper: Solve linear system using LU decomposition +function solveLU(A: number[][], b: number[]): number[] { + // Implement LU decomposition and forward/backward substitution + // (Use implementation from Phase 2 linear algebra) + const n = A.length + const { L, U, P } = luDecomposition(A) + + // Solve Ly = Pb + const Pb = permute(b, P) + const y = forwardSubstitution(L, Pb) + + // Solve Ux = y + const x = backwardSubstitution(U, y) + + return x +} + +// Helper functions +function norm2(v: number[]): number { + return Math.sqrt(v.reduce((sum, x) => sum + x * x, 0)) +} + +function dotProduct(a: number[], b: number[]): number { + return a.reduce((sum, ai, i) => sum + ai * b[i], 0) +} + +function vectorAdd(a: number[], b: number[]): number[] { + return a.map((ai, i) => ai + b[i]) +} + +function vectorSubtract(a: number[], b: number[]): number[] { + return a.map((ai, i) => ai - b[i]) +} + +function scalarMultiply(v: number[], s: number): number[] { + return v.map(x => x * s) +} + +function matrixVectorMultiply(A: number[][], v: number[]): number[] { + return A.map(row => dotProduct(row, v)) +} + +function invertMatrix(A: number[][]): number[][] { + // Use Gauss-Jordan elimination or LU decomposition + // (Implementation from Phase 2) + const n = A.length + const augmented = A.map((row, i) => [ + ...row, + ...Array(n).fill(0).map((_, j) => (i === j ? 1 : 0)) + ]) + + // Gauss-Jordan elimination + for (let i = 0; i < n; i++) { + // Find pivot + let maxRow = i + for (let k = i + 1; k < n; k++) { + if (Math.abs(augmented[k][i]) > Math.abs(augmented[maxRow][i])) { + maxRow = k + } + } + + [augmented[i], augmented[maxRow]] = [augmented[maxRow], augmented[i]] + + // Scale pivot row + const pivot = augmented[i][i] + for (let j = 0; j < 2 * n; j++) { + augmented[i][j] /= pivot + } + + // Eliminate column + for (let k = 0; k < n; k++) { + if (k !== i) { + const factor = augmented[k][i] + for (let j = 0; j < 2 * n; j++) { + augmented[k][j] -= factor * augmented[i][j] + } + } + } + } + + // Extract inverse from augmented matrix + return augmented.map(row => row.slice(n)) +} +``` + +### Trust Region Method (Alternative) + +Instead of line search, use trust region: + +```typescript +function trustRegionNewton( + F: (x: number[]) => number[], + x0: number[], + options: FsolveOptions & { initialRadius?: number } = {} +): FsolveResult { + + const { initialRadius = 1.0 } = options + let radius = initialRadius + + // ... (setup similar to fsolve) + + while (iter < maxIter) { + // Solve trust region subproblem: + // min_p { m(p) = F + J·p + ½p^T·H·p } subject to ||p|| ≤ radius + + // Simplified: use Cauchy point (steepest descent within trust region) + const gradPhi = matrixVectorMultiply(transposeMatrix(J), Fx) + const Hg = matrixVectorMultiply(J, gradPhi) // Gauss-Newton approx: H ≈ J^T·J + + let tau = 1.0 + const normGrad = norm2(gradPhi) + const gHg = dotProduct(gradPhi, Hg) + + if (gHg > 0) { + tau = Math.min(1.0, normGrad ** 3 / (radius * gHg)) + } + + const p = scalarMultiply(gradPhi, -tau * radius / normGrad) + + // Evaluate actual vs predicted reduction + const xNew = vectorAdd(x, p) + const FxNew = F(xNew) + + const actualReduction = 0.5 * (normFx ** 2 - norm2(FxNew) ** 2) + const predictedReduction = -dotProduct(gradPhi, p) - 0.5 * dotProduct(p, Hg) + + const rho = actualReduction / predictedReduction + + // Update trust region radius + if (rho < 0.25) { + radius *= 0.25 + } else if (rho > 0.75 && Math.abs(norm2(p) - radius) < 1e-10) { + radius *= 2.0 + } + + // Accept or reject step + if (rho > 0.1) { + x = xNew + Fx = FxNew + normFx = norm2(FxNew) + } + + // ... (convergence check) + } +} +``` + +--- + +## Task 5.5: Golden Section Search + +### Overview + +Golden section search is a technique for finding extrema of unimodal functions on an interval. It uses the golden ratio to iteratively reduce the search interval. + +### Mathematical Foundation + +**Golden Ratio**: +``` +φ = (1 + √5) / 2 ≈ 1.618034 +``` + +**Key Property**: +``` +1/φ = φ - 1 ≈ 0.618034 +``` + +**Unimodal Function**: f has single minimum on [a, b]: +``` +x₁ < x₂ < x₃ ⟹ f(x₁) > f(x₂) < f(x₃) +``` + +### Algorithm + +``` +Given: [a, b] bracket with minimum inside + +Step 1: Choose interior points using golden ratio: + c = b - (b - a) / φ + d = a + (b - a) / φ + +Step 2: Evaluate f(c) and f(d) + +Step 3: Reduce interval: + If f(c) < f(d): + new interval = [a, d] + next evaluation at: new_c = new_b - (new_b - new_a) / φ + Else: + new interval = [c, b] + next evaluation at: new_d = new_a + (new_b - new_a) / φ + +Step 4: Repeat until |b - a| < tolerance +``` + +### Interval Reduction Pseudocode + +```typescript +const PHI = (1 + Math.sqrt(5)) / 2 // Golden ratio +const RESPHI = 2 - PHI // 1/φ = 0.618... + +function goldenSectionSearch( + f: (x: number) => number, + a: number, + b: number, + options: { + tol?: number, + maxIter?: number, + minimize?: boolean // true = find minimum, false = find maximum + } = {} +): { x: number, fx: number, iterations: number } { + + const { + tol = 1e-8, + maxIter = 100, + minimize = true + } = options + + // Comparison function + const compare = minimize + ? (f1: number, f2: number) => f1 < f2 + : (f1: number, f2: number) => f1 > f2 + + // Step 1: Initialize interior points + let c = b - (b - a) * RESPHI + let d = a + (b - a) * RESPHI + + let fc = f(c) + let fd = f(d) + let funcEvals = 2 + + // Main iteration loop + for (let iter = 0; iter < maxIter; iter++) { + // Convergence check + if (Math.abs(b - a) < tol) { + const xMin = (a + b) / 2 + return { + x: xMin, + fx: f(xMin), + iterations: iter + } + } + + // Reduce interval based on function values + if (compare(fc, fd)) { + // Minimum in [a, d] + b = d + d = c + fd = fc + + c = b - (b - a) * RESPHI + fc = f(c) + funcEvals++ + } else { + // Minimum in [c, b] + a = c + c = d + fc = fd + + d = a + (b - a) * RESPHI + fd = f(d) + funcEvals++ + } + } + + // Return midpoint + const xMin = (a + b) / 2 + return { + x: xMin, + fx: f(xMin), + iterations: maxIter + } +} +``` + +### Convergence Analysis + +**Reduction Factor**: Each iteration reduces interval by factor of φ: +``` +(bₙ₊₁ - aₙ₊₁) = (bₙ - aₙ) / φ +``` + +**After n iterations**: +``` +(bₙ - aₙ) = (b₀ - a₀) / φⁿ +``` + +**Number of iterations** to achieve tolerance ε: +``` +n = ⌈log((b₀ - a₀) / ε) / log(φ)⌉ ≈ 2.078 · log((b₀ - a₀) / ε) +``` + +**Optimality**: Golden section search is optimal in the sense that it minimizes the worst-case number of function evaluations for unimodal functions. + +**Convergence Rate**: Linear with ratio 1/φ ≈ 0.618 + +--- + +## Task 5.6: fminbnd (Bounded 1D Optimization) + +### Overview + +`fminbnd` finds minimum of single-variable function on bounded interval [a, b]. Uses Brent's method for optimization, which combines parabolic interpolation with golden section search. + +### Brent's Method for Optimization + +**Idea**: Similar to Brent's root-finding, but for optimization: +1. Try parabolic interpolation through three points +2. Fall back to golden section if parabolic step is unreliable +3. Maintains bracket around minimum + +### Parabolic Interpolation + +Given three points (x₁, f₁), (x₂, f₂), (x₃, f₃), fit parabola: +``` +p(x) = A(x - x₂)² + B(x - x₂) + C +``` + +Minimum of parabola at: +``` +x_min = x₂ - ½ · (x₂ - x₁)²(f₂ - f₃) - (x₂ - x₃)²(f₂ - f₁) + ───────────────────────────────────────────── + (x₂ - x₁)(f₂ - f₃) - (x₂ - x₃)(f₂ - f₁) +``` + +### Complete Implementation Pseudocode + +```typescript +interface FminbndOptions { + tol?: number + maxIter?: number + display?: boolean +} + +interface FminbndResult { + x: number // Location of minimum + fx: number // Function value at minimum + iterations: number + funcEvals: number + status: string +} + +function fminbnd( + f: (x: number) => number, + a: number, + b: number, + options: FminbndOptions = {} +): FminbndResult { + + const { + tol = 1e-8, + maxIter = 100, + display = false + } = options + + const PHI = (1 + Math.sqrt(5)) / 2 + const RESPHI = 2 - PHI + const SQRT_EPS = Math.sqrt(2.220446049250313e-16) + + // Step 1: Initialize + // Start with golden section points + let x = a + RESPHI * (b - a) // Current best estimate + let w = x // Second best point + let v = x // Previous value of w + + let fx = f(x) + let fw = fx + let fv = fx + + let funcEvals = 1 + let iter = 0 + + // For parabolic interpolation + let d = 0 // Step size + let e = 0 // Step size from iteration before last + + // Main iteration loop + while (iter < maxIter) { + const xm = 0.5 * (a + b) // Midpoint + const tol1 = SQRT_EPS * Math.abs(x) + tol / 3 + const tol2 = 2 * tol1 + + // Convergence check + if (Math.abs(x - xm) <= tol2 - 0.5 * (b - a)) { + return { + x, + fx, + iterations: iter, + funcEvals, + status: 'success' + } + } + + let u: number // New trial point + let useGolden = false + + // Try parabolic interpolation + if (Math.abs(e) > tol1) { + // Fit parabola through x, w, v + const r = (x - w) * (fx - fv) + const q = (x - v) * (fx - fw) + let p = (x - v) * q - (x - w) * r + let qDenom = 2 * (q - r) + + if (qDenom > 0) { + p = -p + } else { + qDenom = -qDenom + } + + // Check if parabolic step is acceptable + const eOld = e + e = d + + const condition1 = Math.abs(p) < Math.abs(0.5 * qDenom * eOld) + const condition2 = p > qDenom * (a - x) + const condition3 = p < qDenom * (b - x) + + if (condition1 && condition2 && condition3) { + // Take parabolic step + d = p / qDenom + u = x + d + + // f must not be evaluated too close to a or b + if (u - a < tol2 || b - u < tol2) { + d = x < xm ? tol1 : -tol1 + } + } else { + useGolden = true + } + } else { + useGolden = true + } + + // Take golden section step if parabolic step not taken + if (useGolden) { + e = x < xm ? b - x : a - x + d = RESPHI * e + } + + // f must not be evaluated too close to x + if (Math.abs(d) >= tol1) { + u = x + d + } else { + u = x + (d > 0 ? tol1 : -tol1) + } + + // Evaluate function at new point + const fu = f(u) + funcEvals++ + + // Update points + if (fu <= fx) { + // u is new best point + if (u < x) { + b = x + } else { + a = x + } + + v = w + fv = fw + w = x + fw = fx + x = u + fx = fu + } else { + // u is not better than x + if (u < x) { + a = u + } else { + b = u + } + + if (fu <= fw || w === x) { + v = w + fv = fw + w = u + fw = fu + } else if (fu <= fv || v === x || v === w) { + v = u + fv = fu + } + } + + iter++ + + if (display) { + console.log(`Iteration ${iter}: x = ${x}, f(x) = ${fx}`) + } + } + + return { + x, + fx, + iterations: iter, + funcEvals, + status: 'max_iterations_reached' + } +} +``` + +### Edge Cases + +```typescript +// Edge case 1: Minimum at boundary +if (a === b) { + return { x: a, fx: f(a), iterations: 0, funcEvals: 1, status: 'success' } +} + +// Edge case 2: Very narrow interval +if (Math.abs(b - a) < tol) { + const x = (a + b) / 2 + return { x, fx: f(x), iterations: 0, funcEvals: 1, status: 'success' } +} + +// Edge case 3: Check boundaries +const fa = f(a) +const fb = f(b) +if (fa < fx) return { x: a, fx: fa, iterations: iter, funcEvals, status: 'minimum_at_lower_bound' } +if (fb < fx) return { x: b, fx: fb, iterations: iter, funcEvals, status: 'minimum_at_upper_bound' } +``` + +--- + +## Task 5.7: Nelder-Mead Simplex + +### Overview + +Nelder-Mead is a derivative-free optimization method for multivariable functions. It uses a simplex (n+1 points in n dimensions) and geometric transformations. + +### Algorithm Operations + +**Four Operations**: +1. **Reflection**: Reflect worst point through centroid +2. **Expansion**: Extend reflection if it's promising +3. **Contraction**: Pull worst point toward centroid +4. **Shrink**: Contract entire simplex toward best point + +**Standard Coefficients**: +``` +α = 1 (reflection) +β = 2 (expansion) +γ = 0.5 (contraction) +δ = 0.5 (shrink) +``` + +### Simplex Initialization + +For n-dimensional problem, create n+1 points: + +```typescript +function initializeSimplex( + x0: number[], + stepSize: number = 0.05 +): number[][] { + + const n = x0.length + const simplex: number[][] = [x0] // First vertex is initial point + + // Create n additional vertices + for (let i = 0; i < n; i++) { + const vertex = [...x0] + + // Perturb i-th coordinate + if (x0[i] !== 0) { + vertex[i] = (1 + stepSize) * x0[i] + } else { + vertex[i] = stepSize + } + + simplex.push(vertex) + } + + return simplex +} +``` + +### Complete Algorithm Pseudocode + +```typescript +interface NelderMeadOptions { + tol?: number // Function value tolerance + xTol?: number // Coordinate tolerance + maxIter?: number + maxFunEvals?: number + initialSimplex?: number[][] // Custom initial simplex + alpha?: number // Reflection coefficient + beta?: number // Expansion coefficient + gamma?: number // Contraction coefficient + delta?: number // Shrink coefficient + display?: boolean +} + +interface NelderMeadResult { + x: number[] + fx: number + iterations: number + funcEvals: number + status: string +} + +function nelderMead( + f: (x: number[]) => number, + x0: number[], + options: NelderMeadOptions = {} +): NelderMeadResult { + + const { + tol = 1e-8, + xTol = 1e-8, + maxIter = 200 * x0.length, + maxFunEvals = 200 * x0.length, + initialSimplex, + alpha = 1.0, // reflection + beta = 2.0, // expansion + gamma = 0.5, // contraction + delta = 0.5, // shrink + display = false + } = options + + const n = x0.length + + // Step 1: Initialize simplex + let simplex = initialSimplex || initializeSimplex(x0) + + // Evaluate at all vertices + let fValues = simplex.map(v => f(v)) + let funcEvals = simplex.length + + let iter = 0 + + // Main loop + while (iter < maxIter && funcEvals < maxFunEvals) { + // Step 2: Sort simplex by function values + const indices = Array.from({ length: n + 1 }, (_, i) => i) + indices.sort((i, j) => fValues[i] - fValues[j]) + + simplex = indices.map(i => simplex[i]) + fValues = indices.map(i => fValues[i]) + + const xBest = simplex[0] + const fBest = fValues[0] + const xWorst = simplex[n] + const fWorst = fValues[n] + const xSecondWorst = simplex[n - 1] + const fSecondWorst = fValues[n - 1] + + // Step 3: Check convergence + // Convergence criterion 1: function value spread + const fSpread = fWorst - fBest + if (fSpread < tol) { + return { + x: xBest, + fx: fBest, + iterations: iter, + funcEvals, + status: 'success' + } + } + + // Convergence criterion 2: simplex size + const simplexSize = Math.max( + ...simplex.slice(1).map(x => + norm2(vectorSubtract(x, xBest)) + ) + ) + if (simplexSize < xTol) { + return { + x: xBest, + fx: fBest, + iterations: iter, + funcEvals, + status: 'success' + } + } + + // Step 4: Compute centroid (excluding worst point) + const centroid = Array(n).fill(0) + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + centroid[j] += simplex[i][j] + } + } + for (let j = 0; j < n; j++) { + centroid[j] /= n + } + + // Step 5: Reflection + const xReflected = Array(n) + for (let j = 0; j < n; j++) { + xReflected[j] = centroid[j] + alpha * (centroid[j] - xWorst[j]) + } + const fReflected = f(xReflected) + funcEvals++ + + if (fReflected < fSecondWorst && fReflected >= fBest) { + // Accept reflection + simplex[n] = xReflected + fValues[n] = fReflected + + if (display) { + console.log(`Iter ${iter}: Reflection, f = ${fBest}`) + } + } else if (fReflected < fBest) { + // Step 6: Expansion + const xExpanded = Array(n) + for (let j = 0; j < n; j++) { + xExpanded[j] = centroid[j] + beta * (xReflected[j] - centroid[j]) + } + const fExpanded = f(xExpanded) + funcEvals++ + + if (fExpanded < fReflected) { + simplex[n] = xExpanded + fValues[n] = fExpanded + + if (display) { + console.log(`Iter ${iter}: Expansion, f = ${fBest}`) + } + } else { + simplex[n] = xReflected + fValues[n] = fReflected + + if (display) { + console.log(`Iter ${iter}: Reflection, f = ${fBest}`) + } + } + } else { + // Step 7: Contraction + let xContracted: number[] + let fContracted: number + + if (fReflected < fWorst) { + // Outside contraction + xContracted = Array(n) + for (let j = 0; j < n; j++) { + xContracted[j] = centroid[j] + gamma * (xReflected[j] - centroid[j]) + } + fContracted = f(xContracted) + funcEvals++ + + if (fContracted <= fReflected) { + simplex[n] = xContracted + fValues[n] = fContracted + + if (display) { + console.log(`Iter ${iter}: Outside contraction, f = ${fBest}`) + } + } else { + // Shrink + performShrink() + } + } else { + // Inside contraction + xContracted = Array(n) + for (let j = 0; j < n; j++) { + xContracted[j] = centroid[j] - gamma * (centroid[j] - xWorst[j]) + } + fContracted = f(xContracted) + funcEvals++ + + if (fContracted < fWorst) { + simplex[n] = xContracted + fValues[n] = fContracted + + if (display) { + console.log(`Iter ${iter}: Inside contraction, f = ${fBest}`) + } + } else { + // Shrink + performShrink() + } + } + } + + // Helper function for shrinking + function performShrink() { + for (let i = 1; i <= n; i++) { + for (let j = 0; j < n; j++) { + simplex[i][j] = xBest[j] + delta * (simplex[i][j] - xBest[j]) + } + fValues[i] = f(simplex[i]) + funcEvals++ + } + + if (display) { + console.log(`Iter ${iter}: Shrink, f = ${fBest}`) + } + } + + iter++ + } + + // Find best point in final simplex + let bestIdx = 0 + for (let i = 1; i <= n; i++) { + if (fValues[i] < fValues[bestIdx]) { + bestIdx = i + } + } + + const status = funcEvals >= maxFunEvals + ? 'max_fun_evals_reached' + : 'max_iterations_reached' + + return { + x: simplex[bestIdx], + fx: fValues[bestIdx], + iterations: iter, + funcEvals, + status + } +} +``` + +### Termination Criteria + +```typescript +// Criterion 1: Function value standard deviation +const fMean = fValues.reduce((sum, f) => sum + f, 0) / (n + 1) +const fStd = Math.sqrt( + fValues.reduce((sum, f) => sum + (f - fMean) ** 2, 0) / (n + 1) +) +if (fStd < tol) { + // Converged +} + +// Criterion 2: Coordinate standard deviation +for (let j = 0; j < n; j++) { + const xj = simplex.map(v => v[j]) + const xjMean = xj.reduce((sum, x) => sum + x, 0) / (n + 1) + const xjStd = Math.sqrt( + xj.reduce((sum, x) => sum + (x - xjMean) ** 2, 0) / (n + 1) + ) + if (xjStd < xTol) { + // Converged in dimension j + } +} + +// Criterion 3: Simplex diameter +const diameter = Math.max( + ...simplex.flatMap((v1, i) => + simplex.slice(i + 1).map(v2 => + norm2(vectorSubtract(v1, v2)) + ) + ) +) +if (diameter < xTol) { + // Converged +} +``` + +### Restart Strategy for Robustness + +```typescript +function nelderMeadWithRestart( + f: (x: number[]) => number, + x0: number[], + options: NelderMeadOptions & { maxRestarts?: number } = {} +): NelderMeadResult { + + const { maxRestarts = 3 } = options + let bestResult: NelderMeadResult | null = null + + for (let restart = 0; restart <= maxRestarts; restart++) { + // Perturb starting point for restarts + const x0Perturbed = restart === 0 + ? x0 + : x0.map(x => x + (Math.random() - 0.5) * Math.abs(x) * 0.1) + + const result = nelderMead(f, x0Perturbed, options) + + if (!bestResult || result.fx < bestResult.fx) { + bestResult = result + } + + if (bestResult.status === 'success') { + break + } + } + + return bestResult! +} +``` + +--- + +## Task 5.8: BFGS (Quasi-Newton) + +### Overview + +BFGS (Broyden-Fletcher-Goldfarb-Shanno) is a quasi-Newton method that approximates the Hessian matrix using gradient information. It's one of the most effective methods for unconstrained optimization. + +### Mathematical Foundation + +**Newton's Method**: +``` +x_{k+1} = x_k - H_k^{-1} ∇f(x_k) +``` +where H is the Hessian matrix. + +**BFGS Approximation**: Maintain approximate inverse Hessian B_k ≈ H_k^{-1} + +**Search Direction**: +``` +p_k = -B_k ∇f(x_k) +``` + +### BFGS Update Formula + +``` +s_k = x_{k+1} - x_k +y_k = ∇f(x_{k+1}) - ∇f(x_k) + +B_{k+1} = B_k + (1 + (y_k^T B_k y_k)/(y_k^T s_k)) · (s_k s_k^T)/(y_k^T s_k) + - (B_k y_k s_k^T + s_k y_k^T B_k)/(y_k^T s_k) +``` + +**Simplified form**: +``` +ρ_k = 1 / (y_k^T s_k) + +B_{k+1} = (I - ρ_k s_k y_k^T) B_k (I - ρ_k y_k s_k^T) + ρ_k s_k s_k^T +``` + +### Line Search (Wolfe Conditions) + +**Strong Wolfe Conditions**: +``` +1. Sufficient decrease (Armijo): + f(x_k + α p_k) ≤ f(x_k) + c_1 α ∇f(x_k)^T p_k + +2. Curvature condition: + |∇f(x_k + α p_k)^T p_k| ≤ c_2 |∇f(x_k)^T p_k| +``` + +Typically: c_1 = 1e-4, c_2 = 0.9 + +### Numerical Gradient Computation + +```typescript +function numericalGradient( + f: (x: number[]) => number, + x: number[], + eps: number = 2.22e-16 +): number[] { + + const n = x.length + const grad = Array(n) + const fx = f(x) + + for (let i = 0; i < n; i++) { + // Optimal step size + const h = Math.sqrt(eps) * Math.max(Math.abs(x[i]), 1.0) + + // Forward difference + const xPerturbed = [...x] + xPerturbed[i] += h + const fPerturbed = f(xPerturbed) + + grad[i] = (fPerturbed - fx) / h + } + + return grad +} + +// More accurate: central difference +function numericalGradientCentral( + f: (x: number[]) => number, + x: number[], + eps: number = 2.22e-16 +): number[] { + + const n = x.length + const grad = Array(n) + + for (let i = 0; i < n; i++) { + const h = Math.cbrt(eps) * Math.max(Math.abs(x[i]), 1.0) + + const xForward = [...x] + const xBackward = [...x] + xForward[i] += h + xBackward[i] -= h + + const fForward = f(xForward) + const fBackward = f(xBackward) + + grad[i] = (fForward - fBackward) / (2 * h) + } + + return grad +} +``` + +### Complete BFGS Implementation + +```typescript +interface BFGSOptions { + tol?: number // Gradient tolerance + xTol?: number // Step tolerance + maxIter?: number + gradient?: (x: number[]) => number[] // Analytical gradient + c1?: number // Armijo constant + c2?: number // Curvature constant + maxLineSearchIter?: number + display?: boolean +} + +interface BFGSResult { + x: number[] + fx: number + gradient: number[] + iterations: number + funcEvals: number + gradEvals: number + status: string +} + +function bfgs( + f: (x: number[]) => number, + x0: number[], + options: BFGSOptions = {} +): BFGSResult { + + const { + tol = 1e-8, + xTol = 1e-8, + maxIter = 200, + gradient, + c1 = 1e-4, + c2 = 0.9, + maxLineSearchIter = 20, + display = false + } = options + + const n = x0.length + const eps = 2.220446049250313e-16 + + // Gradient function (analytical or numerical) + const grad = gradient || ((x: number[]) => numericalGradientCentral(f, x, eps)) + + // Step 1: Initialize + let x = [...x0] + let fx = f(x) + let gx = grad(x) + + let funcEvals = 1 + let gradEvals = 1 + let iter = 0 + + // Initialize inverse Hessian approximation as identity + let B = Array(n).fill(0).map((_, i) => + Array(n).fill(0).map((_, j) => (i === j ? 1 : 0)) + ) + + // Main iteration loop + while (iter < maxIter) { + // Step 2: Check convergence + const gradNorm = norm2(gx) + + if (gradNorm < tol) { + return { + x, + fx, + gradient: gx, + iterations: iter, + funcEvals, + gradEvals, + status: 'success' + } + } + + // Step 3: Compute search direction + const p = matrixVectorMultiply(B, scalarMultiply(gx, -1)) + + // Ensure descent direction + const gpDot = dotProduct(gx, p) + if (gpDot >= 0) { + // Not a descent direction, reset to steepest descent + console.warn('BFGS: Not a descent direction, resetting to steepest descent') + for (let i = 0; i < n; i++) { + p[i] = -gx[i] + } + // Reset B to identity + B = Array(n).fill(0).map((_, i) => + Array(n).fill(0).map((_, j) => (i === j ? 1 : 0)) + ) + } + + // Step 4: Line search satisfying Wolfe conditions + const lineSearchResult = lineSearchWolfe( + f, + grad, + x, + fx, + gx, + p, + { c1, c2, maxIter: maxLineSearchIter } + ) + + const alpha = lineSearchResult.alpha + const xNew = lineSearchResult.x + const fxNew = lineSearchResult.fx + const gxNew = lineSearchResult.gx + + funcEvals += lineSearchResult.funcEvals + gradEvals += lineSearchResult.gradEvals + + if (alpha === 0) { + return { + x, + fx, + gradient: gx, + iterations: iter, + funcEvals, + gradEvals, + status: 'line_search_failed' + } + } + + // Step 5: Compute s_k and y_k + const s = vectorSubtract(xNew, x) + const y = vectorSubtract(gxNew, gx) + + const sTy = dotProduct(s, y) + + // Step 6: BFGS update (only if curvature condition satisfied) + if (sTy > 1e-10 * norm2(s) * norm2(y)) { + // Compute ρ_k + const rho = 1 / sTy + + // Compute I - ρ s y^T + const I = Array(n).fill(0).map((_, i) => + Array(n).fill(0).map((_, j) => (i === j ? 1 : 0)) + ) + + const sy_T = outerProduct(s, y) + const ys_T = outerProduct(y, s) + + const left = matrixSubtract(I, scalarMatrixMultiply(sy_T, rho)) + const right = matrixSubtract(I, scalarMatrixMultiply(ys_T, rho)) + + // B_{k+1} = left * B_k * right + rho * s * s^T + const ss_T = outerProduct(s, s) + const term1 = matrixMultiply(matrixMultiply(left, B), right) + const term2 = scalarMatrixMultiply(ss_T, rho) + + B = matrixAdd(term1, term2) + } else { + // Curvature condition not satisfied, skip update + if (display) { + console.warn(`BFGS: Skipping update (curvature condition violated)`) + } + } + + // Step 7: Check step size convergence + const stepNorm = norm2(s) + const relStepNorm = stepNorm / (norm2(x) + 1e-14) + + // Update for next iteration + x = xNew + fx = fxNew + gx = gxNew + iter++ + + if (display) { + console.log( + `Iter ${iter}: f = ${fx.toExponential(6)}, ` + + `||g|| = ${gradNorm.toExponential(4)}, ` + + `α = ${alpha.toExponential(4)}` + ) + } + + if (stepNorm < xTol || relStepNorm < xTol) { + return { + x, + fx, + gradient: gx, + iterations: iter, + funcEvals, + gradEvals, + status: 'step_too_small' + } + } + } + + return { + x, + fx, + gradient: gx, + iterations: iter, + funcEvals, + gradEvals, + status: 'max_iterations_reached' + } +} + +// Line search satisfying Wolfe conditions +function lineSearchWolfe( + f: (x: number[]) => number, + grad: (x: number[]) => number[], + x: number[], + fx: number, + gx: number[], + p: number[], + options: { + c1: number, + c2: number, + maxIter: number, + alphaMax?: number + } +): { + alpha: number, + x: number[], + fx: number, + gx: number[], + funcEvals: number, + gradEvals: number +} { + + const { c1, c2, maxIter, alphaMax = 10.0 } = options + + let alpha = 1.0 // Initial step size + const phi0 = fx + const dphi0 = dotProduct(gx, p) + + let funcEvals = 0 + let gradEvals = 0 + + // Check if p is descent direction + if (dphi0 >= 0) { + return { + alpha: 0, + x, + fx, + gx, + funcEvals, + gradEvals + } + } + + let alphaLow = 0 + let alphaHigh = alphaMax + + for (let iter = 0; iter < maxIter; iter++) { + const xNew = vectorAdd(x, scalarMultiply(p, alpha)) + const fxNew = f(xNew) + funcEvals++ + + const phi = fxNew + + // Check Armijo condition (sufficient decrease) + if (phi > phi0 + c1 * alpha * dphi0) { + // Armijo violated, reduce step size + alphaHigh = alpha + alpha = (alphaLow + alphaHigh) / 2 + continue + } + + // Compute gradient at new point + const gxNew = grad(xNew) + gradEvals++ + + const dphi = dotProduct(gxNew, p) + + // Check curvature condition + if (Math.abs(dphi) <= -c2 * dphi0) { + // Both Wolfe conditions satisfied + return { + alpha, + x: xNew, + fx: fxNew, + gx: gxNew, + funcEvals, + gradEvals + } + } + + // Curvature condition violated + if (dphi >= 0) { + // Slope changed sign, reduce step + alphaHigh = alpha + alpha = (alphaLow + alphaHigh) / 2 + } else { + // Slope still negative, increase step + alphaLow = alpha + if (alphaHigh >= alphaMax) { + alpha = 2 * alpha + } else { + alpha = (alphaLow + alphaHigh) / 2 + } + } + } + + // Line search failed to satisfy Wolfe conditions + // Return best point found + const xBest = vectorAdd(x, scalarMultiply(p, alpha)) + return { + alpha, + x: xBest, + fx: f(xBest), + gx: grad(xBest), + funcEvals: funcEvals + 1, + gradEvals: gradEvals + 1 + } +} + +// Helper: outer product +function outerProduct(a: number[], b: number[]): number[][] { + return a.map(ai => b.map(bj => ai * bj)) +} + +// Helper: matrix operations +function matrixAdd(A: number[][], B: number[][]): number[][] { + return A.map((row, i) => row.map((val, j) => val + B[i][j])) +} + +function matrixSubtract(A: number[][], B: number[][]): number[][] { + return A.map((row, i) => row.map((val, j) => val - B[i][j])) +} + +function scalarMatrixMultiply(A: number[][], s: number): number[][] { + return A.map(row => row.map(val => val * s)) +} + +function matrixMultiply(A: number[][], B: number[][]): number[][] { + const m = A.length + const n = B[0].length + const p = B.length + + const C = Array(m).fill(0).map(() => Array(n).fill(0)) + + for (let i = 0; i < m; i++) { + for (let j = 0; j < n; j++) { + for (let k = 0; k < p; k++) { + C[i][j] += A[i][k] * B[k][j] + } + } + } + + return C +} +``` + +### Limited-Memory BFGS (L-BFGS) + +For large-scale problems, store only recent updates: + +```typescript +interface LBFGSOptions extends BFGSOptions { + m?: number // Number of corrections to store (default: 10) +} + +function lbfgs( + f: (x: number[]) => number, + x0: number[], + options: LBFGSOptions = {} +): BFGSResult { + + const { m = 10 } = options + + // Store recent s and y vectors + const sHistory: number[][] = [] + const yHistory: number[][] = [] + const rhoHistory: number[] = [] + + // ... (main loop similar to BFGS) + + // Instead of storing full B matrix, use two-loop recursion + function computeSearchDirection(g: number[]): number[] { + const q = [...g] + const n = g.length + const k = sHistory.length + const alpha = Array(k) + + // First loop (backward) + for (let i = k - 1; i >= 0; i--) { + alpha[i] = rhoHistory[i] * dotProduct(sHistory[i], q) + for (let j = 0; j < n; j++) { + q[j] -= alpha[i] * yHistory[i][j] + } + } + + // Scale by initial Hessian estimate + let gamma = 1.0 + if (k > 0) { + gamma = dotProduct(sHistory[k - 1], yHistory[k - 1]) / + dotProduct(yHistory[k - 1], yHistory[k - 1]) + } + + const r = q.map(qi => gamma * qi) + + // Second loop (forward) + for (let i = 0; i < k; i++) { + const beta = rhoHistory[i] * dotProduct(yHistory[i], r) + for (let j = 0; j < n; j++) { + r[j] += sHistory[i][j] * (alpha[i] - beta) + } + } + + return r.map(ri => -ri) // Negative for descent direction + } + + // After line search, update history + function updateHistory(s: number[], y: number[]) { + const sTy = dotProduct(s, y) + + if (sTy > 1e-10) { + sHistory.push(s) + yHistory.push(y) + rhoHistory.push(1 / sTy) + + // Remove oldest if exceeds memory limit + if (sHistory.length > m) { + sHistory.shift() + yHistory.shift() + rhoHistory.shift() + } + } + } +} +``` + +--- + +## Integration and Testing Strategy + +### Unit Tests + +```typescript +// Test 1: Brent's method on polynomial +test('brent: finds root of x^3 - 2x - 5', () => { + const f = (x: number) => x ** 3 - 2 * x - 5 + const result = brent(f, 2, 3) + assert(Math.abs(result.root - 2.0945514815) < 1e-8) +}) + +// Test 2: Newton-Raphson with analytical derivative +test('newton: square root', () => { + const f = (x: number) => x * x - 2 + const fp = (x: number) => 2 * x + const result = newton(f, 1.5, { derivative: fp }) + assert(Math.abs(result.root - Math.sqrt(2)) < 1e-10) +}) + +// Test 3: fsolve on 2D system +test('fsolve: 2D nonlinear system', () => { + const F = (x: number[]) => [ + x[0] ** 2 + x[1] ** 2 - 1, + x[0] - x[1] + ] + const result = fsolve(F, [0.5, 0.5]) + assert(Math.abs(result.solution[0] - Math.sqrt(0.5)) < 1e-8) +}) + +// Test 4: BFGS on Rosenbrock +test('bfgs: Rosenbrock function', () => { + const f = (x: number[]) => (1 - x[0]) ** 2 + 100 * (x[1] - x[0] ** 2) ** 2 + const result = bfgs(f, [-1.2, 1]) + assert(Math.abs(result.x[0] - 1) < 1e-4) + assert(Math.abs(result.x[1] - 1) < 1e-4) +}) +``` + +### Performance Benchmarks + +Measure against: +- SciPy (Python) +- GNU Scientific Library (GSL) +- Numerical Recipes + +Target: Within 2x of C implementations for pure JavaScript, match or beat for WASM. + +--- + +## WASM Acceleration Opportunities + +### High-Priority Functions for WASM + +1. **Matrix operations in fsolve**: Jacobian computation, LU decomposition +2. **Line search loops**: Tight iteration loops benefit from WASM +3. **BFGS matrix updates**: Dense matrix operations +4. **Numerical differentiation**: Repeated function evaluations + +### Memory Layout for WASM + +```wat +;; Store simplex vertices contiguously +(memory $vertices (;..;)) + +;; Store function values +(memory $fvalues (;..;)) + +;; Centroid computation in WASM +(func $compute_centroid (param $n i32) (result f64) + ;; Efficient SIMD operations +) +``` + +--- + +## Success Criteria + +- [ ] All 8 functions implemented in TypeScript +- [ ] Comprehensive test coverage (>95%) +- [ ] Benchmarks show 2-5x speedup with WASM +- [ ] Documentation with examples +- [ ] Integration with existing math.js API +- [ ] Backward compatible with current interface + +--- + +## References + +1. **Brent, R. P.** (1973). *Algorithms for Minimization Without Derivatives*. Prentice-Hall. +2. **Nocedal, J., & Wright, S. J.** (2006). *Numerical Optimization* (2nd ed.). Springer. +3. **Press, W. H., et al.** (2007). *Numerical Recipes: The Art of Scientific Computing* (3rd ed.). Cambridge University Press. +4. **Powell, M. J. D.** (1970). "A hybrid method for nonlinear equations." *Numerical Methods for Nonlinear Algebraic Equations*, 87-114. +5. **Broyden, C. G.** (1965). "A class of methods for solving nonlinear simultaneous equations." *Mathematics of Computation*, 19(92), 577-593. +6. **Nelder, J. A., & Mead, R.** (1965). "A simplex method for function minimization." *The Computer Journal*, 7(4), 308-313. +7. **Moré, J. J., & Thuente, D. J.** (1994). "Line search algorithms with guaranteed sufficient decrease." *ACM Transactions on Mathematical Software*, 20(3), 286-307. diff --git a/SCIENTIFIC_COMPUTING_IMPROVEMENT_PLAN.md b/SCIENTIFIC_COMPUTING_IMPROVEMENT_PLAN.md new file mode 100644 index 0000000000..cfa17db571 --- /dev/null +++ b/SCIENTIFIC_COMPUTING_IMPROVEMENT_PLAN.md @@ -0,0 +1,1688 @@ +# MathJS Scientific Computing Improvement Plan + +## Executive Summary + +This document outlines a comprehensive plan to transform mathjs into a full-featured scientific computing library comparable to NumPy/SciPy, MATLAB, and Mathematica. The plan is organized into 15 sprints with 5-10 tasks each, designed for parallel agent implementation. + +### Current State Analysis +- **Total Functions Implemented**: ~214 functions across 18 categories +- **TypeScript Conversion**: 9% complete (61/673 files) +- **WASM Integration**: Established infrastructure with matrix operations, FFT, and basic numeric functions +- **Overall Scientific Computing Completeness**: ~35% + +### Target State +- **Goal**: 95%+ scientific computing capability parity with SciPy +- **New Functions to Add**: ~200+ functions across all domains +- **Performance**: WASM-accelerated for computationally intensive operations + +--- + +## Sprint Organization + +| Sprint | Focus Area | Tasks | Priority | Complexity | +|--------|-----------|-------|----------|------------| +| 1 | SVD & Core Linear Algebra | 8 | CRITICAL | High | +| 2 | Bessel & Airy Functions | 10 | CRITICAL | Medium | +| 3 | Probability Distributions (Part 1) | 10 | CRITICAL | Medium | +| 4 | Probability Distributions (Part 2) | 10 | CRITICAL | Medium | +| 5 | Root Finding & Optimization | 8 | CRITICAL | High | +| 6 | Numerical Integration | 8 | HIGH | Medium | +| 7 | Interpolation & Curve Fitting | 8 | HIGH | Medium | +| 8 | Elliptic & Hypergeometric Functions | 10 | HIGH | High | +| 9 | Advanced Statistics | 10 | HIGH | Medium | +| 10 | Signal Processing Filters | 10 | MEDIUM-HIGH | Medium | +| 11 | Window Functions & Spectral | 8 | MEDIUM-HIGH | Low | +| 12 | ODE/PDE Enhancements | 8 | MEDIUM | High | +| 13 | Orthogonal Polynomials | 8 | MEDIUM | Medium | +| 14 | Additional Special Functions | 10 | MEDIUM | Medium | +| 15 | Symbolic Computation Enhancements | 8 | MEDIUM | High | + +--- + +## Sprint 1: SVD & Core Linear Algebra Decompositions +**Priority**: CRITICAL +**Estimated Complexity**: High +**Dependencies**: None (foundational) + +### Background +SVD (Singular Value Decomposition) is the most critical missing function. It's fundamental for: +- Least squares solutions +- Matrix rank computation +- Principal Component Analysis (PCA) +- Image compression +- Solving ill-conditioned systems +- Pseudoinverse computation (current pinv.ts has TODO: "Use SVD instead") + +### Tasks + +#### Task 1.1: Implement SVD (Singular Value Decomposition) +**File**: `src/function/algebra/decomposition/svd.ts` +**Description**: Implement full SVD decomposition A = U * S * V^T +**Algorithms**: +- Golub-Kahan bidiagonalization +- QR iteration for singular values +- Option for thin vs full SVD +**Interface**: +```typescript +svd(A: Matrix): { U: Matrix, S: Vector, V: Matrix } +svd(A: Matrix, full: boolean): { U: Matrix, S: Matrix, V: Matrix } +``` +**Tests**: Reconstruct A from U*S*V^T, orthogonality of U and V, singular values ordering +**WASM**: Yes - implement in `src-wasm/algebra/svd.ts` + +#### Task 1.2: Implement Cholesky Decomposition +**File**: `src/function/algebra/decomposition/cholesky.ts` +**Description**: Decompose symmetric positive definite matrix A = L * L^T +**Interface**: +```typescript +cholesky(A: Matrix): Matrix // Returns lower triangular L +cholesky(A: Matrix, 'upper'): Matrix // Returns upper triangular U +``` +**Tests**: Symmetry check, positive definiteness check, reconstruction +**WASM**: Yes - often 2x faster than LU for symmetric systems + +#### Task 1.3: Implement Matrix Rank via SVD +**File**: `src/function/matrix/rank.ts` +**Description**: Compute numerical rank using SVD singular value thresholding +**Interface**: +```typescript +rank(A: Matrix): number +rank(A: Matrix, tol: number): number // Custom tolerance +``` +**Tests**: Full rank, rank deficient, numerical tolerance + +#### Task 1.4: Implement Null Space (Kernel) +**File**: `src/function/matrix/nullspace.ts` +**Description**: Compute orthonormal basis for null space using SVD +**Interface**: +```typescript +nullspace(A: Matrix): Matrix // Columns are null space basis vectors +nullspace(A: Matrix, tol: number): Matrix +``` +**Tests**: A * nullspace(A) ≈ 0, orthonormality of result + +#### Task 1.5: Implement Column Space (Range) +**File**: `src/function/matrix/orth.ts` +**Description**: Compute orthonormal basis for column space using SVD +**Interface**: +```typescript +orth(A: Matrix): Matrix // Orthonormal basis for range(A) +``` +**Tests**: Orthonormality, span verification + +#### Task 1.6: Implement Least Squares Solver (lstsq) +**File**: `src/function/algebra/solver/lstsq.ts` +**Description**: Solve overdetermined systems using SVD +**Interface**: +```typescript +lstsq(A: Matrix, b: Vector): { x: Vector, residuals: Vector, rank: number, s: Vector } +``` +**Tests**: Exact solutions, overdetermined, underdetermined, rank-deficient + +#### Task 1.7: Implement Polar Decomposition +**File**: `src/function/algebra/decomposition/polar.ts` +**Description**: Decompose A = U * P where U is unitary and P is positive semidefinite +**Interface**: +```typescript +polar(A: Matrix): { U: Matrix, P: Matrix } +``` +**Tests**: Reconstruction, U unitary, P positive semidefinite + +#### Task 1.8: Update pinv to Use SVD +**File**: `src/function/matrix/pinv.ts` (modification) +**Description**: Refactor pseudoinverse to use SVD implementation +**Current Issue**: Line 20 has TODO: "Use SVD instead (may improve precision)" +**Tests**: Compare with current implementation, precision improvement verification + +--- + +## Sprint 2: Bessel & Airy Functions +**Priority**: CRITICAL +**Estimated Complexity**: Medium +**Dependencies**: None + +### Background +Bessel functions are essential for physics and engineering applications: +- Wave propagation in cylindrical coordinates +- Heat conduction +- Electromagnetic theory +- Quantum mechanics +- Vibration analysis + +### Tasks + +#### Task 2.1: Implement Bessel J (First Kind) +**File**: `src/function/special/besselj.ts` +**Description**: Bessel functions of the first kind J_n(x) +**Algorithms**: +- Miller's backward recurrence for integer orders +- Series expansion for small x +- Asymptotic expansion for large x +**Interface**: +```typescript +besselj(n: number, x: number): number // J_n(x) +besselj(n: number, x: Matrix): Matrix // Vectorized +``` +**Special cases**: j0, j1 optimized implementations +**Tests**: Known values, recurrence relations, limiting behaviors + +#### Task 2.2: Implement Bessel Y (Second Kind) +**File**: `src/function/special/bessely.ts` +**Description**: Bessel functions of the second kind Y_n(x) (Neumann functions) +**Interface**: +```typescript +bessely(n: number, x: number): number // Y_n(x) +``` +**Tests**: Wronskian relation with J, singularity at x=0 + +#### Task 2.3: Implement Modified Bessel I +**File**: `src/function/special/besseli.ts` +**Description**: Modified Bessel functions of the first kind I_n(x) +**Interface**: +```typescript +besseli(n: number, x: number): number // I_n(x) +``` +**Tests**: Exponential growth, relation to J + +#### Task 2.4: Implement Modified Bessel K +**File**: `src/function/special/besselk.ts` +**Description**: Modified Bessel functions of the second kind K_n(x) +**Interface**: +```typescript +besselk(n: number, x: number): number // K_n(x) +``` +**Tests**: Exponential decay, Wronskian with I + +#### Task 2.5: Implement Spherical Bessel j +**File**: `src/function/special/sphericalbesselj.ts` +**Description**: Spherical Bessel functions j_n(x) +**Relation**: j_n(x) = sqrt(π/2x) * J_{n+1/2}(x) +**Interface**: +```typescript +sphericalbesselj(n: number, x: number): number +``` +**Tests**: Known values, recurrence relations + +#### Task 2.6: Implement Spherical Bessel y +**File**: `src/function/special/sphericalbessely.ts` +**Description**: Spherical Bessel functions y_n(x) +**Interface**: +```typescript +sphericalbessely(n: number, x: number): number +``` + +#### Task 2.7: Implement Hankel Functions +**File**: `src/function/special/hankel.ts` +**Description**: Hankel functions H1_n(x) and H2_n(x) +**Interface**: +```typescript +hankel1(n: number, x: number): Complex // H1_n(x) = J_n(x) + i*Y_n(x) +hankel2(n: number, x: number): Complex // H2_n(x) = J_n(x) - i*Y_n(x) +``` + +#### Task 2.8: Implement Airy Ai Function +**File**: `src/function/special/airy.ts` +**Description**: Airy function Ai(x) and derivative Ai'(x) +**Interface**: +```typescript +airy(x: number): { ai: number, aip: number, bi: number, bip: number } +airyai(x: number): number +airyaip(x: number): number // Derivative +``` +**Tests**: Differential equation, asymptotic behavior + +#### Task 2.9: Implement Airy Bi Function +**File**: `src/function/special/airy.ts` (extension) +**Description**: Airy function Bi(x) and derivative Bi'(x) +**Interface**: +```typescript +airybi(x: number): number +airybip(x: number): number // Derivative +``` + +#### Task 2.10: WASM Acceleration for Bessel Functions +**File**: `src-wasm/special/bessel.ts` +**Description**: High-performance WASM implementations of Bessel functions +**Focus**: j0, j1, y0, y1, i0, i1, k0, k1 (most commonly used) +**Expected speedup**: 3-5x for large arrays + +--- + +## Sprint 3: Probability Distributions (Part 1 - Continuous) +**Priority**: CRITICAL +**Estimated Complexity**: Medium +**Dependencies**: erf (exists), gamma (exists) + +### Background +Probability distributions are fundamental for statistics, Monte Carlo methods, and data science. Each distribution needs: pdf, cdf, quantile (ppf/icdf), and random sampling. + +### Tasks + +#### Task 3.1: Implement Normal Distribution +**File**: `src/function/probability/distributions/normal.ts` +**Description**: Gaussian/Normal distribution N(μ, σ²) +**Interface**: +```typescript +normalPdf(x: number, mu?: number, sigma?: number): number +normalCdf(x: number, mu?: number, sigma?: number): number +normalInv(p: number, mu?: number, sigma?: number): number // Quantile +normalRandom(mu?: number, sigma?: number): number +normalRandom(mu?: number, sigma?: number, size: number[]): Matrix +``` +**Algorithms**: +- PDF: Gaussian formula +- CDF: Uses existing erf +- Inverse CDF: Rational approximation (Abramowitz & Stegun) +- Random: Box-Muller or Ziggurat algorithm +**Tests**: Mean/variance of samples, CDF(quantile(p)) = p + +#### Task 3.2: Implement Student's t Distribution +**File**: `src/function/probability/distributions/studentt.ts` +**Description**: Student's t distribution for small sample statistics +**Interface**: +```typescript +tPdf(x: number, df: number): number +tCdf(x: number, df: number): number +tInv(p: number, df: number): number +tRandom(df: number, size?: number[]): number | Matrix +``` +**Tests**: Approaches normal as df → ∞ + +#### Task 3.3: Implement Chi-Square Distribution +**File**: `src/function/probability/distributions/chi2.ts` +**Description**: Chi-square distribution for variance estimation and hypothesis testing +**Interface**: +```typescript +chi2Pdf(x: number, df: number): number +chi2Cdf(x: number, df: number): number +chi2Inv(p: number, df: number): number +chi2Random(df: number, size?: number[]): number | Matrix +``` +**Relation**: Uses gamma distribution (χ² = Gamma(df/2, 2)) + +#### Task 3.4: Implement F Distribution +**File**: `src/function/probability/distributions/fdist.ts` +**Description**: F distribution for ANOVA and variance ratio tests +**Interface**: +```typescript +fPdf(x: number, df1: number, df2: number): number +fCdf(x: number, df1: number, df2: number): number +fInv(p: number, df1: number, df2: number): number +fRandom(df1: number, df2: number, size?: number[]): number | Matrix +``` + +#### Task 3.5: Implement Beta Distribution +**File**: `src/function/probability/distributions/beta.ts` +**Description**: Beta distribution for proportions and Bayesian analysis +**Interface**: +```typescript +betaPdf(x: number, alpha: number, beta: number): number +betaCdf(x: number, alpha: number, beta: number): number +betaInv(p: number, alpha: number, beta: number): number +betaRandom(alpha: number, beta: number, size?: number[]): number | Matrix +``` +**Requires**: Beta function and incomplete beta function + +#### Task 3.6: Implement Exponential Distribution +**File**: `src/function/probability/distributions/exponential.ts` +**Description**: Exponential distribution for waiting times +**Interface**: +```typescript +exponentialPdf(x: number, lambda: number): number +exponentialCdf(x: number, lambda: number): number +exponentialInv(p: number, lambda: number): number +exponentialRandom(lambda: number, size?: number[]): number | Matrix +``` + +#### Task 3.7: Implement Gamma Distribution +**File**: `src/function/probability/distributions/gammadist.ts` +**Description**: Full gamma distribution (extends existing gamma function) +**Interface**: +```typescript +gammaPdf(x: number, shape: number, scale: number): number +gammaCdf(x: number, shape: number, scale: number): number +gammaInv(p: number, shape: number, scale: number): number +gammaRandom(shape: number, scale: number, size?: number[]): number | Matrix +``` +**Requires**: Incomplete gamma function (gammainc) + +#### Task 3.8: Implement Incomplete Gamma Function +**File**: `src/function/special/gammainc.ts` +**Description**: Lower and upper incomplete gamma functions +**Interface**: +```typescript +gammainc(a: number, x: number): number // P(a, x) = γ(a,x)/Γ(a) +gammaincc(a: number, x: number): number // Q(a, x) = Γ(a,x)/Γ(a) +``` +**Algorithms**: Series expansion, continued fraction + +#### Task 3.9: Implement Weibull Distribution +**File**: `src/function/probability/distributions/weibull.ts` +**Description**: Weibull distribution for reliability analysis +**Interface**: +```typescript +weibullPdf(x: number, shape: number, scale: number): number +weibullCdf(x: number, shape: number, scale: number): number +weibullInv(p: number, shape: number, scale: number): number +weibullRandom(shape: number, scale: number, size?: number[]): number | Matrix +``` + +#### Task 3.10: Implement Lognormal Distribution +**File**: `src/function/probability/distributions/lognormal.ts` +**Description**: Lognormal distribution for multiplicative processes +**Interface**: +```typescript +lognormalPdf(x: number, mu: number, sigma: number): number +lognormalCdf(x: number, mu: number, sigma: number): number +lognormalInv(p: number, mu: number, sigma: number): number +lognormalRandom(mu: number, sigma: number, size?: number[]): number | Matrix +``` + +--- + +## Sprint 4: Probability Distributions (Part 2 - Discrete & Additional) +**Priority**: CRITICAL +**Estimated Complexity**: Medium +**Dependencies**: Sprint 3 (some shared utilities) + +### Tasks + +#### Task 4.1: Implement Poisson Distribution +**File**: `src/function/probability/distributions/poisson.ts` +**Description**: Poisson distribution for count data +**Interface**: +```typescript +poissonPmf(k: number, lambda: number): number // P(X = k) +poissonCdf(k: number, lambda: number): number // P(X ≤ k) +poissonInv(p: number, lambda: number): number +poissonRandom(lambda: number, size?: number[]): number | Matrix +``` + +#### Task 4.2: Implement Binomial Distribution +**File**: `src/function/probability/distributions/binomial.ts` +**Description**: Binomial distribution for success/failure trials +**Interface**: +```typescript +binomialPmf(k: number, n: number, p: number): number +binomialCdf(k: number, n: number, p: number): number +binomialInv(q: number, n: number, p: number): number +binomialRandom(n: number, p: number, size?: number[]): number | Matrix +``` + +#### Task 4.3: Implement Negative Binomial Distribution +**File**: `src/function/probability/distributions/negativebinomial.ts` +**Description**: Negative binomial for number of trials until r successes +**Interface**: +```typescript +negativeBinomialPmf(k: number, r: number, p: number): number +negativeBinomialCdf(k: number, r: number, p: number): number +negativeBinomialRandom(r: number, p: number, size?: number[]): number | Matrix +``` + +#### Task 4.4: Implement Geometric Distribution +**File**: `src/function/probability/distributions/geometric.ts` +**Description**: Geometric distribution for waiting time to first success +**Interface**: +```typescript +geometricPmf(k: number, p: number): number +geometricCdf(k: number, p: number): number +geometricRandom(p: number, size?: number[]): number | Matrix +``` + +#### Task 4.5: Implement Hypergeometric Distribution +**File**: `src/function/probability/distributions/hypergeometric.ts` +**Description**: Hypergeometric for sampling without replacement +**Interface**: +```typescript +hypergeometricPmf(k: number, N: number, K: number, n: number): number +hypergeometricCdf(k: number, N: number, K: number, n: number): number +hypergeometricRandom(N: number, K: number, n: number, size?: number[]): number | Matrix +``` + +#### Task 4.6: Implement Uniform Distribution (Continuous) +**File**: `src/function/probability/distributions/uniform.ts` +**Description**: Continuous uniform distribution +**Interface**: +```typescript +uniformPdf(x: number, a: number, b: number): number +uniformCdf(x: number, a: number, b: number): number +uniformInv(p: number, a: number, b: number): number +uniformRandom(a: number, b: number, size?: number[]): number | Matrix +``` + +#### Task 4.7: Implement Cauchy Distribution +**File**: `src/function/probability/distributions/cauchy.ts` +**Description**: Cauchy/Lorentz distribution (heavy tails) +**Interface**: +```typescript +cauchyPdf(x: number, x0: number, gamma: number): number +cauchyCdf(x: number, x0: number, gamma: number): number +cauchyInv(p: number, x0: number, gamma: number): number +cauchyRandom(x0: number, gamma: number, size?: number[]): number | Matrix +``` + +#### Task 4.8: Implement Logistic Distribution +**File**: `src/function/probability/distributions/logistic.ts` +**Description**: Logistic distribution for growth modeling +**Interface**: +```typescript +logisticPdf(x: number, mu: number, s: number): number +logisticCdf(x: number, mu: number, s: number): number +logisticInv(p: number, mu: number, s: number): number +logisticRandom(mu: number, s: number, size?: number[]): number | Matrix +``` + +#### Task 4.9: Implement Incomplete Beta Function +**File**: `src/function/special/betainc.ts` +**Description**: Regularized incomplete beta function (needed for many distributions) +**Interface**: +```typescript +betainc(a: number, b: number, x: number): number // I_x(a, b) +betaincinv(a: number, b: number, y: number): number // Inverse +``` +**Algorithms**: Continued fraction, series expansion + +#### Task 4.10: Implement Multivariate Normal Distribution +**File**: `src/function/probability/distributions/mvnormal.ts` +**Description**: Multivariate normal distribution for correlated random vectors +**Interface**: +```typescript +mvnormalPdf(x: Vector, mean: Vector, cov: Matrix): number +mvnormalRandom(mean: Vector, cov: Matrix, size?: number): Matrix +``` +**Requires**: Cholesky decomposition (Sprint 1) + +--- + +## Sprint 5: Root Finding & Optimization +**Priority**: CRITICAL +**Estimated Complexity**: High +**Dependencies**: Sprint 1 (linear algebra for some methods) + +### Background +Root finding and optimization are fundamental to scientific computing - used in solving equations, parameter estimation, machine learning, and engineering design. + +### Tasks + +#### Task 5.1: Implement Brent's Method (Root Finding) +**File**: `src/function/numeric/roots/brentq.ts` +**Description**: Brent's method for scalar root finding (bracketed) +**Interface**: +```typescript +brentq(f: Function, a: number, b: number, options?: { tol?: number, maxIter?: number }): number +``` +**Algorithm**: Combines bisection, secant, and inverse quadratic interpolation +**Tests**: Polynomial roots, transcendental equations + +#### Task 5.2: Implement Newton-Raphson Method +**File**: `src/function/numeric/roots/newton.ts` +**Description**: Newton's method for root finding +**Interface**: +```typescript +newton(f: Function, x0: number, options?: { fprime?: Function, tol?: number }): number +``` +**Features**: Automatic differentiation if fprime not provided +**Tests**: Convergence rate, starting point sensitivity + +#### Task 5.3: Implement fzero (General Root Finder) +**File**: `src/function/numeric/fzero.ts` +**Description**: General-purpose scalar root finding (like MATLAB's fzero) +**Interface**: +```typescript +fzero(f: Function, x0: number | [number, number]): number +``` +**Strategy**: Bracketing if interval given, otherwise combination of methods + +#### Task 5.4: Implement fsolve (Nonlinear System Solver) +**File**: `src/function/numeric/fsolve.ts` +**Description**: Solve system of nonlinear equations F(x) = 0 +**Interface**: +```typescript +fsolve(F: Function, x0: Vector, options?: { jacobian?: Function, tol?: number }): Vector +``` +**Algorithms**: Newton-Raphson with line search, Broyden's method +**Requires**: Linear system solver (lusolve) + +#### Task 5.5: Implement Golden Section Search +**File**: `src/function/numeric/optimize/golden.ts` +**Description**: Golden section search for 1D minimization (no derivatives) +**Interface**: +```typescript +goldenSection(f: Function, a: number, b: number, options?: { tol?: number }): number +``` + +#### Task 5.6: Implement fminbnd (Bounded 1D Optimization) +**File**: `src/function/numeric/fminbnd.ts` +**Description**: Find minimum of single-variable function on bounded interval +**Interface**: +```typescript +fminbnd(f: Function, a: number, b: number, options?: { tol?: number }): { x: number, fval: number } +``` +**Algorithm**: Brent's method for optimization + +#### Task 5.7: Implement Nelder-Mead Simplex +**File**: `src/function/numeric/optimize/neldermead.ts` +**Description**: Derivative-free multidimensional optimization +**Interface**: +```typescript +nelderMead(f: Function, x0: Vector, options?: NelderMeadOptions): OptimizeResult +``` +**Tests**: Rosenbrock function, quadratic functions + +#### Task 5.8: Implement BFGS (Quasi-Newton) +**File**: `src/function/numeric/optimize/bfgs.ts` +**Description**: BFGS quasi-Newton method for unconstrained optimization +**Interface**: +```typescript +bfgs(f: Function, x0: Vector, options?: { grad?: Function, tol?: number }): OptimizeResult +``` +**Features**: Numerical gradient if not provided +**Tests**: Quadratic convergence rate + +--- + +## Sprint 6: Numerical Integration (Quadrature) +**Priority**: HIGH +**Estimated Complexity**: Medium +**Dependencies**: None + +### Background +Numerical integration is essential for computing areas, expected values, solving differential equations, and many scientific applications. + +### Tasks + +#### Task 6.1: Implement Trapezoidal Rule +**File**: `src/function/numeric/integration/trapz.ts` +**Description**: Trapezoidal rule for numerical integration +**Interface**: +```typescript +trapz(y: Vector, x?: Vector): number // Integrate y over x +trapz(y: Matrix, dim?: number): Vector // Along dimension +cumtrapz(y: Vector, x?: Vector): Vector // Cumulative +``` +**Tests**: Compare with analytical integrals + +#### Task 6.2: Implement Simpson's Rule +**File**: `src/function/numeric/integration/simps.ts` +**Description**: Simpson's rule (higher accuracy than trapezoidal) +**Interface**: +```typescript +simps(y: Vector, x?: Vector): number +``` +**Requires**: Odd number of points (uses composite rule) + +#### Task 6.3: Implement Adaptive Quadrature (quad) +**File**: `src/function/numeric/integration/quad.ts` +**Description**: Adaptive quadrature for definite integrals +**Interface**: +```typescript +quad(f: Function, a: number, b: number, options?: QuadOptions): { result: number, error: number } +``` +**Algorithm**: Gauss-Kronrod adaptive +**Features**: Error estimation, subdivision + +#### Task 6.4: Implement Gauss-Legendre Quadrature +**File**: `src/function/numeric/integration/gausslegendre.ts` +**Description**: Fixed-order Gauss-Legendre quadrature +**Interface**: +```typescript +gausslegendre(f: Function, a: number, b: number, n?: number): number +gausslegendreNodes(n: number): { nodes: Vector, weights: Vector } +``` +**Tests**: Exact for polynomials up to degree 2n-1 + +#### Task 6.5: Implement Romberg Integration +**File**: `src/function/numeric/integration/romberg.ts` +**Description**: Romberg integration using Richardson extrapolation +**Interface**: +```typescript +romberg(f: Function, a: number, b: number, options?: { tol?: number }): number +``` + +#### Task 6.6: Implement Infinite Interval Integration +**File**: `src/function/numeric/integration/quadinf.ts` +**Description**: Integration over infinite intervals +**Interface**: +```typescript +quadinf(f: Function, a: number, b: number): number // a or b can be ±Infinity +``` +**Algorithm**: Variable transformation + adaptive quadrature + +#### Task 6.7: Implement 2D Integration (dblquad) +**File**: `src/function/numeric/integration/dblquad.ts` +**Description**: Double integration over rectangular or general regions +**Interface**: +```typescript +dblquad(f: Function, ax: number, bx: number, ay: number | Function, by: number | Function): number +``` + +#### Task 6.8: Implement Monte Carlo Integration +**File**: `src/function/numeric/integration/montecarlo.ts` +**Description**: Monte Carlo integration for high-dimensional integrals +**Interface**: +```typescript +montecarloIntegrate(f: Function, bounds: Array<[number, number]>, n?: number): { result: number, error: number } +``` + +--- + +## Sprint 7: Interpolation & Curve Fitting +**Priority**: HIGH +**Estimated Complexity**: Medium +**Dependencies**: Sprint 1 (linear algebra for splines) + +### Tasks + +#### Task 7.1: Implement Linear Interpolation (interp1) +**File**: `src/function/numeric/interpolate/interp1.ts` +**Description**: 1D interpolation with multiple methods +**Interface**: +```typescript +interp1(x: Vector, y: Vector, xq: number | Vector, method?: 'linear' | 'nearest' | 'spline' | 'pchip'): number | Vector +``` +**Tests**: Exact at data points, boundary behavior + +#### Task 7.2: Implement Cubic Spline Interpolation +**File**: `src/function/numeric/interpolate/spline.ts` +**Description**: Natural cubic spline interpolation +**Interface**: +```typescript +spline(x: Vector, y: Vector): SplineFunction +// SplineFunction.evaluate(xq: number): number +// SplineFunction.derivative(xq: number): number +// SplineFunction.integral(a: number, b: number): number +``` +**Algorithm**: Tridiagonal system for spline coefficients + +#### Task 7.3: Implement PCHIP (Monotonic Interpolation) +**File**: `src/function/numeric/interpolate/pchip.ts` +**Description**: Piecewise Cubic Hermite Interpolating Polynomial +**Interface**: +```typescript +pchip(x: Vector, y: Vector): PchipFunction +``` +**Advantage**: Preserves monotonicity, no overshooting + +#### Task 7.4: Implement 2D Interpolation (interp2) +**File**: `src/function/numeric/interpolate/interp2.ts` +**Description**: Bilinear and bicubic interpolation +**Interface**: +```typescript +interp2(x: Vector, y: Vector, z: Matrix, xq: number, yq: number, method?: 'linear' | 'cubic'): number +``` + +#### Task 7.5: Implement Polynomial Fitting (polyfit) +**File**: `src/function/numeric/fitting/polyfit.ts` +**Description**: Least squares polynomial fitting +**Interface**: +```typescript +polyfit(x: Vector, y: Vector, n: number): Vector // Coefficients [a_n, ..., a_1, a_0] +polyval(p: Vector, x: number | Vector): number | Vector // Evaluate polynomial +``` +**Uses**: Linear least squares + +#### Task 7.6: Implement General Curve Fitting +**File**: `src/function/numeric/fitting/curvefit.ts` +**Description**: Nonlinear least squares curve fitting +**Interface**: +```typescript +curvefit(f: Function, x: Vector, y: Vector, p0: Vector, options?: CurveFitOptions): { params: Vector, residuals: Vector } +``` +**Algorithm**: Levenberg-Marquardt + +#### Task 7.7: Implement Regression (Linear) +**File**: `src/function/statistics/regression/linearRegression.ts` +**Description**: Linear regression with statistics +**Interface**: +```typescript +linearRegression(x: Vector | Matrix, y: Vector): { + coefficients: Vector, + rSquared: number, + standardErrors: Vector, + pValues: Vector +} +``` + +#### Task 7.8: Implement Polynomial Root Finding (roots) +**File**: `src/function/algebra/roots.ts` +**Description**: Find all roots of a polynomial (companion matrix method) +**Interface**: +```typescript +roots(coefficients: Vector): Vector // All roots including complex +``` +**Algorithm**: Eigenvalues of companion matrix +**Extends**: Existing polynomialRoot (limited to degree 3) + +--- + +## Sprint 8: Elliptic & Hypergeometric Functions +**Priority**: HIGH +**Estimated Complexity**: High +**Dependencies**: None (but builds on Sprint 2 patterns) + +### Background +Elliptic functions are essential for: +- Pendulum motion +- Elliptical orbits +- Electromagnetic field calculations +- Cryptography (elliptic curves) + +Hypergeometric functions generalize many special functions. + +### Tasks + +#### Task 8.1: Implement Complete Elliptic Integral K +**File**: `src/function/special/elliptic.ts` +**Description**: Complete elliptic integral of the first kind K(m) +**Interface**: +```typescript +ellipk(m: number): number // K(m), m = k² +``` +**Algorithm**: Arithmetic-geometric mean (AGM) +**Tests**: Known values, limiting behaviors + +#### Task 8.2: Implement Complete Elliptic Integral E +**File**: `src/function/special/elliptic.ts` (extension) +**Description**: Complete elliptic integral of the second kind E(m) +**Interface**: +```typescript +ellipe(m: number): number // E(m) +``` + +#### Task 8.3: Implement Incomplete Elliptic Integrals +**File**: `src/function/special/elliptic.ts` (extension) +**Description**: Incomplete elliptic integrals F(φ, m) and E(φ, m) +**Interface**: +```typescript +ellipkinc(phi: number, m: number): number // F(φ, m) +ellipeinc(phi: number, m: number): number // E(φ, m) +``` +**Algorithm**: Carlson's forms or Legendre reduction + +#### Task 8.4: Implement Elliptic Integral of Third Kind +**File**: `src/function/special/elliptic.ts` (extension) +**Description**: Π(n, φ, m) - third kind +**Interface**: +```typescript +ellippi(n: number, phi: number, m: number): number +``` + +#### Task 8.5: Implement Jacobi Elliptic Functions +**File**: `src/function/special/jacobi.ts` +**Description**: Jacobi elliptic functions sn, cn, dn +**Interface**: +```typescript +ellipj(u: number, m: number): { sn: number, cn: number, dn: number } +jacobisn(u: number, m: number): number +jacobicn(u: number, m: number): number +jacobidn(u: number, m: number): number +``` +**Algorithm**: Arithmetic-geometric mean descent + +#### Task 8.6: Implement Confluent Hypergeometric 1F1 +**File**: `src/function/special/hypergeometric.ts` +**Description**: Kummer's confluent hypergeometric function M(a, b, z) +**Interface**: +```typescript +hyp1f1(a: number, b: number, z: number): number // ₁F₁(a; b; z) +``` +**Algorithm**: Series expansion, asymptotic expansion, connection formulas + +#### Task 8.7: Implement Gauss Hypergeometric 2F1 +**File**: `src/function/special/hypergeometric.ts` (extension) +**Description**: Gauss hypergeometric function F(a, b; c; z) +**Interface**: +```typescript +hyp2f1(a: number, b: number, c: number, z: number): number // ₂F₁(a, b; c; z) +``` +**Algorithm**: Series for |z| < 1, analytic continuation otherwise +**Importance**: Generalizes many special functions + +#### Task 8.8: Implement Tricomi's Function U +**File**: `src/function/special/hypergeometric.ts` (extension) +**Description**: Tricomi's confluent hypergeometric U(a, b, z) +**Interface**: +```typescript +hypU(a: number, b: number, z: number): number +``` + +#### Task 8.9: Implement Generalized Hypergeometric pFq +**File**: `src/function/special/hypergeometric.ts` (extension) +**Description**: Generalized hypergeometric function +**Interface**: +```typescript +hyppfq(a: number[], b: number[], z: number): number // pFq +``` + +#### Task 8.10: Implement Carlson Elliptic Integrals +**File**: `src/function/special/carlson.ts` +**Description**: Carlson symmetric forms RF, RD, RJ, RC +**Interface**: +```typescript +carlsonRF(x: number, y: number, z: number): number +carlsonRD(x: number, y: number, z: number): number +carlsonRJ(x: number, y: number, z: number, p: number): number +carlsonRC(x: number, y: number): number +``` +**Use**: More numerically stable for elliptic integrals + +--- + +## Sprint 9: Advanced Statistics +**Priority**: HIGH +**Estimated Complexity**: Medium +**Dependencies**: Sprint 3-4 (probability distributions) + +### Tasks + +#### Task 9.1: Implement T-Test +**File**: `src/function/statistics/hypothesis/ttest.ts` +**Description**: Student's t-test for hypothesis testing +**Interface**: +```typescript +ttest(x: Vector, mu?: number): { t: number, pValue: number, ci: [number, number] } // One-sample +ttest2(x: Vector, y: Vector, options?: { paired?: boolean }): TTestResult // Two-sample +``` + +#### Task 9.2: Implement Chi-Square Test +**File**: `src/function/statistics/hypothesis/chi2test.ts` +**Description**: Chi-square goodness of fit and independence tests +**Interface**: +```typescript +chi2gof(observed: Vector, expected?: Vector): { chi2: number, pValue: number, df: number } +chi2test(contingencyTable: Matrix): { chi2: number, pValue: number } +``` + +#### Task 9.3: Implement F-Test +**File**: `src/function/statistics/hypothesis/ftest.ts` +**Description**: F-test for variance equality +**Interface**: +```typescript +ftest(x: Vector, y: Vector): { f: number, pValue: number } +``` + +#### Task 9.4: Implement ANOVA +**File**: `src/function/statistics/anova.ts` +**Description**: One-way and two-way ANOVA +**Interface**: +```typescript +anova1(groups: Vector[]): AnovaResult +anova2(data: Matrix, factorA: Vector, factorB: Vector): Anova2Result +``` + +#### Task 9.5: Implement Kolmogorov-Smirnov Test +**File**: `src/function/statistics/hypothesis/kstest.ts` +**Description**: KS test for distribution comparison +**Interface**: +```typescript +kstest(x: Vector, cdf: Function): { d: number, pValue: number } +kstest2(x: Vector, y: Vector): { d: number, pValue: number } // Two-sample +``` + +#### Task 9.6: Implement Skewness and Kurtosis +**File**: `src/function/statistics/moments.ts` +**Description**: Higher-order moments +**Interface**: +```typescript +skewness(x: Vector): number +kurtosis(x: Vector): number +moment(x: Vector, n: number): number // nth central moment +``` + +#### Task 9.7: Implement Covariance Matrix +**File**: `src/function/statistics/cov.ts` +**Description**: Covariance and correlation matrices +**Interface**: +```typescript +cov(X: Matrix): Matrix // Sample covariance +corrcoef(X: Matrix): Matrix // Correlation matrix +``` + +#### Task 9.8: Implement Bootstrap Methods +**File**: `src/function/statistics/bootstrap.ts` +**Description**: Bootstrap confidence intervals and standard errors +**Interface**: +```typescript +bootstrap(data: Vector, statistic: Function, n?: number): BootstrapResult +``` + +#### Task 9.9: Implement Kernel Density Estimation +**File**: `src/function/statistics/kde.ts` +**Description**: Nonparametric density estimation +**Interface**: +```typescript +kde(data: Vector, bandwidth?: number): KDEFunction +// KDEFunction.evaluate(x: number): number +// KDEFunction.pdf(x: Vector): Vector +``` + +#### Task 9.10: Implement Quantile and Percentile +**File**: `src/function/statistics/quantile.ts` +**Description**: Enhanced quantile computation (extend quantileSeq) +**Interface**: +```typescript +quantile(data: Vector, q: number | Vector, method?: number): number | Vector +percentile(data: Vector, p: number | Vector): number | Vector +iqr(data: Vector): number // Interquartile range +``` + +--- + +## Sprint 10: Signal Processing Filters +**Priority**: MEDIUM-HIGH +**Estimated Complexity**: Medium +**Dependencies**: Sprint 1 (linear algebra), existing fft/ifft + +### Tasks + +#### Task 10.1: Implement Butterworth Filter Design +**File**: `src/function/signal/filters/butter.ts` +**Description**: Butterworth IIR filter design +**Interface**: +```typescript +butter(n: number, Wn: number | [number, number], btype?: 'low' | 'high' | 'band' | 'stop'): { b: Vector, a: Vector } +``` +**Tests**: Maximally flat passband + +#### Task 10.2: Implement Chebyshev Type I Filter +**File**: `src/function/signal/filters/cheby1.ts` +**Description**: Chebyshev Type I filter design +**Interface**: +```typescript +cheby1(n: number, rp: number, Wn: number | [number, number], btype?: string): { b: Vector, a: Vector } +``` +**rp**: Passband ripple in dB + +#### Task 10.3: Implement Chebyshev Type II Filter +**File**: `src/function/signal/filters/cheby2.ts` +**Description**: Chebyshev Type II filter design +**Interface**: +```typescript +cheby2(n: number, rs: number, Wn: number | [number, number], btype?: string): { b: Vector, a: Vector } +``` +**rs**: Stopband attenuation in dB + +#### Task 10.4: Implement Elliptic Filter +**File**: `src/function/signal/filters/ellip.ts` +**Description**: Elliptic (Cauer) filter design +**Interface**: +```typescript +ellip(n: number, rp: number, rs: number, Wn: number | [number, number], btype?: string): { b: Vector, a: Vector } +``` +**Dependencies**: Elliptic functions (Sprint 8) + +#### Task 10.5: Implement FIR Filter Design (fir1) +**File**: `src/function/signal/filters/fir1.ts` +**Description**: Window-based FIR filter design +**Interface**: +```typescript +fir1(n: number, Wn: number | [number, number], ftype?: string, window?: string | Vector): Vector +``` + +#### Task 10.6: Implement filter Function +**File**: `src/function/signal/filter.ts` +**Description**: Apply digital filter to data +**Interface**: +```typescript +filter(b: Vector, a: Vector, x: Vector): Vector +filtfilt(b: Vector, a: Vector, x: Vector): Vector // Zero-phase filtering +``` + +#### Task 10.7: Implement Convolution +**File**: `src/function/signal/conv.ts` +**Description**: 1D and 2D convolution +**Interface**: +```typescript +conv(u: Vector, v: Vector, shape?: 'full' | 'same' | 'valid'): Vector +conv2(A: Matrix, K: Matrix, shape?: 'full' | 'same' | 'valid'): Matrix +``` +**WASM**: Yes - use FFT-based convolution for large arrays + +#### Task 10.8: Implement Correlation +**File**: `src/function/signal/xcorr.ts` +**Description**: Cross-correlation +**Interface**: +```typescript +xcorr(x: Vector, y?: Vector, maxlag?: number): { c: Vector, lags: Vector } +``` + +#### Task 10.9: Implement Second-Order Sections +**File**: `src/function/signal/sos.ts` +**Description**: Second-order sections filter representation +**Interface**: +```typescript +tf2sos(b: Vector, a: Vector): Matrix // Convert to SOS +sos2tf(sos: Matrix): { b: Vector, a: Vector } +sosfilt(sos: Matrix, x: Vector): Vector // Filter using SOS +``` +**Advantage**: Better numerical stability than tf + +#### Task 10.10: Implement Transfer Function Conversions +**File**: `src/function/signal/tfconvert.ts` +**Description**: Convert between filter representations +**Interface**: +```typescript +tf2ss(b: Vector, a: Vector): { A: Matrix, B: Matrix, C: Matrix, D: Matrix } +ss2tf(A: Matrix, B: Matrix, C: Matrix, D: Matrix): { b: Vector, a: Vector } +zpk2sos(z: Vector, p: Vector, k: number): Matrix +``` + +--- + +## Sprint 11: Window Functions & Spectral Analysis +**Priority**: MEDIUM-HIGH +**Estimated Complexity**: Low +**Dependencies**: Sprint 10 + +### Tasks + +#### Task 11.1: Implement Hamming Window +**File**: `src/function/signal/windows/hamming.ts` +**Description**: Hamming window function +**Interface**: +```typescript +hamming(n: number, symmetric?: boolean): Vector +``` + +#### Task 11.2: Implement Hanning Window +**File**: `src/function/signal/windows/hanning.ts` +**Description**: Hanning (Hann) window function +**Interface**: +```typescript +hanning(n: number, symmetric?: boolean): Vector +``` + +#### Task 11.3: Implement Blackman Window +**File**: `src/function/signal/windows/blackman.ts` +**Description**: Blackman window function +**Interface**: +```typescript +blackman(n: number, symmetric?: boolean): Vector +``` + +#### Task 11.4: Implement Kaiser Window +**File**: `src/function/signal/windows/kaiser.ts` +**Description**: Kaiser window with adjustable parameter +**Interface**: +```typescript +kaiser(n: number, beta: number): Vector +``` +**Dependencies**: Bessel I0 (Sprint 2) + +#### Task 11.5: Implement Additional Windows +**File**: `src/function/signal/windows/windows.ts` +**Description**: Collection of additional windows +**Interface**: +```typescript +bartlett(n: number): Vector +triang(n: number): Vector +boxcar(n: number): Vector +tukey(n: number, alpha?: number): Vector +parzen(n: number): Vector +``` + +#### Task 11.6: Implement Periodogram +**File**: `src/function/signal/spectral/periodogram.ts` +**Description**: Power spectral density estimation +**Interface**: +```typescript +periodogram(x: Vector, fs?: number, window?: Vector): { psd: Vector, f: Vector } +``` + +#### Task 11.7: Implement Welch's Method +**File**: `src/function/signal/spectral/pwelch.ts` +**Description**: Welch's method for PSD estimation +**Interface**: +```typescript +pwelch(x: Vector, window?: number | Vector, noverlap?: number, nfft?: number, fs?: number): { psd: Vector, f: Vector } +``` + +#### Task 11.8: Implement Short-Time Fourier Transform +**File**: `src/function/signal/spectral/stft.ts` +**Description**: STFT for time-frequency analysis +**Interface**: +```typescript +stft(x: Vector, window: number | Vector, noverlap?: number, nfft?: number): { S: Matrix, f: Vector, t: Vector } +istft(S: Matrix, window: number | Vector, noverlap?: number): Vector +``` + +--- + +## Sprint 12: ODE/PDE Enhancements +**Priority**: MEDIUM +**Estimated Complexity**: High +**Dependencies**: Sprint 1 (linear algebra) + +### Tasks + +#### Task 12.1: Implement Backward Differentiation Formula (BDF) +**File**: `src/function/numeric/ode/bdf.ts` +**Description**: BDF method for stiff ODEs +**Interface**: +```typescript +solveBDF(odefun: Function, tspan: [number, number], y0: Vector, options?: BDFOptions): ODESolution +``` +**Use**: Stiff systems where RK methods fail + +#### Task 12.2: Implement Adams-Bashforth-Moulton +**File**: `src/function/numeric/ode/adams.ts` +**Description**: Adams-Bashforth-Moulton predictor-corrector +**Interface**: +```typescript +solveABM(odefun: Function, tspan: [number, number], y0: Vector, options?: ABMOptions): ODESolution +``` + +#### Task 12.3: Implement Event Detection +**File**: `src/function/numeric/ode/events.ts` +**Description**: Event detection and handling for ODEs +**Interface**: +```typescript +// Extended solveODE options +{ + events: (t: number, y: Vector) => number[], // Zero-crossing functions + terminal: boolean[], // Stop on event? + direction: number[] // Direction of crossing +} +``` + +#### Task 12.4: Implement DAE Solver +**File**: `src/function/numeric/dae/solveDAE.ts` +**Description**: Differential-algebraic equation solver +**Interface**: +```typescript +solveDAE(F: Function, tspan: [number, number], y0: Vector, yp0: Vector): DAESolution +``` +**Algorithm**: BDF-based method + +#### Task 12.5: Implement Boundary Value Problem Solver +**File**: `src/function/numeric/bvp/bvp4c.ts` +**Description**: Solve two-point BVPs +**Interface**: +```typescript +bvp4c(odefun: Function, bcfun: Function, solinit: BVPInit): BVPSolution +``` +**Algorithm**: Collocation method + +#### Task 12.6: Implement Finite Difference PDE Solver +**File**: `src/function/numeric/pde/parabolic.ts` +**Description**: Solve parabolic PDEs (heat equation type) +**Interface**: +```typescript +pdepe(m: number, pdefun: Function, icfun: Function, bcfun: Function, xmesh: Vector, tspan: Vector): PDESolution +``` + +#### Task 12.7: Implement Method of Lines +**File**: `src/function/numeric/pde/mol.ts` +**Description**: Method of lines for PDE → ODE conversion +**Interface**: +```typescript +methodOfLines(pde: PDESpec, xmesh: Vector): (t: number, y: Vector) => Vector // Returns ODE function +``` + +#### Task 12.8: Implement Delay Differential Equations +**File**: `src/function/numeric/dde/solveDDE.ts` +**Description**: Solve DDEs with constant delays +**Interface**: +```typescript +solveDDE(ddefun: Function, delays: Vector, tspan: [number, number], history: Function): DDESolution +``` + +--- + +## Sprint 13: Orthogonal Polynomials +**Priority**: MEDIUM +**Estimated Complexity**: Medium +**Dependencies**: None + +### Tasks + +#### Task 13.1: Implement Legendre Polynomials +**File**: `src/function/special/polynomials/legendre.ts` +**Description**: Legendre polynomials P_n(x) +**Interface**: +```typescript +legendre(n: number, x: number): number // P_n(x) +legendreP(n: number, x: number): number // Same as legendre +assocLegendre(l: number, m: number, x: number): number // Associated P_l^m(x) +``` +**Tests**: Orthogonality, recurrence relations + +#### Task 13.2: Implement Hermite Polynomials +**File**: `src/function/special/polynomials/hermite.ts` +**Description**: Hermite polynomials (physicist's and probabilist's) +**Interface**: +```typescript +hermite(n: number, x: number): number // Physicist's H_n(x) +hermiteProb(n: number, x: number): number // Probabilist's He_n(x) +``` +**Use**: Quantum harmonic oscillator, Gaussian quadrature + +#### Task 13.3: Implement Laguerre Polynomials +**File**: `src/function/special/polynomials/laguerre.ts` +**Description**: Laguerre polynomials L_n(x) +**Interface**: +```typescript +laguerre(n: number, x: number): number // L_n(x) +assocLaguerre(n: number, alpha: number, x: number): number // L_n^α(x) +``` +**Use**: Hydrogen atom wavefunctions + +#### Task 13.4: Implement Chebyshev Polynomials +**File**: `src/function/special/polynomials/chebyshev.ts` +**Description**: Chebyshev polynomials T_n(x) and U_n(x) +**Interface**: +```typescript +chebyshevT(n: number, x: number): number // First kind +chebyshevU(n: number, x: number): number // Second kind +``` +**Use**: Numerical approximation, spectral methods + +#### Task 13.5: Implement Jacobi Polynomials +**File**: `src/function/special/polynomials/jacobi.ts` +**Description**: Jacobi polynomials P_n^(α,β)(x) +**Interface**: +```typescript +jacobi(n: number, alpha: number, beta: number, x: number): number +``` +**Note**: Generalizes Legendre, Chebyshev, Gegenbauer + +#### Task 13.6: Implement Gegenbauer Polynomials +**File**: `src/function/special/polynomials/gegenbauer.ts` +**Description**: Gegenbauer (ultraspherical) polynomials +**Interface**: +```typescript +gegenbauer(n: number, alpha: number, x: number): number // C_n^α(x) +``` + +#### Task 13.7: Implement Spherical Harmonics +**File**: `src/function/special/sphericalharmonics.ts` +**Description**: Spherical harmonics Y_l^m(θ, φ) +**Interface**: +```typescript +sphericalHarmonic(l: number, m: number, theta: number, phi: number): Complex +sphericalHarmonicReal(l: number, m: number, theta: number, phi: number): number +``` +**Dependencies**: Associated Legendre (Task 13.1) + +#### Task 13.8: Implement Polynomial Roots and Weights +**File**: `src/function/special/polynomials/quadrature.ts` +**Description**: Compute quadrature nodes and weights from orthogonal polynomials +**Interface**: +```typescript +legendreRoots(n: number): { roots: Vector, weights: Vector } +hermiteRoots(n: number): { roots: Vector, weights: Vector } +laguerreRoots(n: number): { roots: Vector, weights: Vector } +chebyshevRoots(n: number): { roots: Vector, weights: Vector } +``` +**Use**: Gaussian quadrature + +--- + +## Sprint 14: Additional Special Functions +**Priority**: MEDIUM +**Estimated Complexity**: Medium +**Dependencies**: Sprints 2, 8 + +### Tasks + +#### Task 14.1: Implement erfc, erfinv, erfcinv +**File**: `src/function/special/erf.ts` (extension) +**Description**: Complementary error function and inverses +**Interface**: +```typescript +erfc(x: number): number // 1 - erf(x) +erfinv(y: number): number // Inverse of erf +erfcinv(y: number): number // Inverse of erfc +``` +**Algorithm**: Rational approximation for inverses + +#### Task 14.2: Implement Digamma and Polygamma +**File**: `src/function/special/digamma.ts` +**Description**: Psi function and derivatives +**Interface**: +```typescript +digamma(x: number): number // ψ(x) = Γ'(x)/Γ(x) +trigamma(x: number): number // ψ'(x) +polygamma(n: number, x: number): number // ψ^(n)(x) +``` + +#### Task 14.3: Implement Lambert W Function +**File**: `src/function/special/lambertw.ts` +**Description**: Lambert W function W(z) where W(z)*e^W(z) = z +**Interface**: +```typescript +lambertw(z: number, k?: number): number // Branch k (default 0) +``` +**Algorithm**: Halley's method + +#### Task 14.4: Implement Exponential Integrals +**File**: `src/function/special/expint.ts` +**Description**: Exponential integral functions +**Interface**: +```typescript +expint(n: number, x: number): number // E_n(x) +ei(x: number): number // Ei(x) +li(x: number): number // Logarithmic integral +``` + +#### Task 14.5: Implement Fresnel Integrals +**File**: `src/function/special/fresnel.ts` +**Description**: Fresnel S and C integrals +**Interface**: +```typescript +fresnelS(x: number): number // S(x) = ∫sin(πt²/2)dt +fresnelC(x: number): number // C(x) = ∫cos(πt²/2)dt +fresnel(x: number): { S: number, C: number } +``` + +#### Task 14.6: Implement Sine and Cosine Integrals +**File**: `src/function/special/trig_integrals.ts` +**Description**: Si, Ci, Shi, Chi integrals +**Interface**: +```typescript +si(x: number): number // Sine integral +ci(x: number): number // Cosine integral +shi(x: number): number // Hyperbolic sine integral +chi(x: number): number // Hyperbolic cosine integral +``` + +#### Task 14.7: Implement Struve Functions +**File**: `src/function/special/struve.ts` +**Description**: Struve functions H_n(x) and L_n(x) +**Interface**: +```typescript +struve(n: number, x: number): number // H_n(x) +struveL(n: number, x: number): number // Modified L_n(x) +``` + +#### Task 14.8: Implement Polylogarithm +**File**: `src/function/special/polylog.ts` +**Description**: Polylogarithm function Li_s(z) +**Interface**: +```typescript +polylog(s: number, z: number): number // Li_s(z) +``` + +#### Task 14.9: Implement Dawson Function +**File**: `src/function/special/dawson.ts` +**Description**: Dawson's integral D(x) +**Interface**: +```typescript +dawson(x: number): number // D(x) = e^(-x²) ∫₀ˣ e^(t²) dt +``` + +#### Task 14.10: Implement Owen's T Function +**File**: `src/function/special/owent.ts` +**Description**: Owen's T function for bivariate normal +**Interface**: +```typescript +owenT(h: number, a: number): number +``` + +--- + +## Sprint 15: Symbolic Computation Enhancements +**Priority**: MEDIUM +**Estimated Complexity**: High +**Dependencies**: Existing expression system + +### Tasks + +#### Task 15.1: Implement Symbolic Integration +**File**: `src/function/algebra/integrate.ts` +**Description**: Symbolic integration (antiderivatives) +**Interface**: +```typescript +integrate(expr: Node | string, variable: string): Node +``` +**Scope**: Polynomials, basic trig, exponentials, logarithms +**Algorithm**: Pattern matching, Risch algorithm subset + +#### Task 15.2: Implement Limits +**File**: `src/function/algebra/limit.ts` +**Description**: Compute limits symbolically +**Interface**: +```typescript +limit(expr: Node | string, variable: string, value: number | string, direction?: '+' | '-'): Node +``` +**Handles**: L'Hôpital's rule, indeterminate forms + +#### Task 15.3: Implement Taylor/Maclaurin Series +**File**: `src/function/algebra/taylor.ts` +**Description**: Taylor series expansion +**Interface**: +```typescript +taylor(expr: Node | string, variable: string, point: number, order: number): Node +``` +**Output**: Polynomial approximation + +#### Task 15.4: Implement Symbolic Summation +**File**: `src/function/algebra/symbolicSum.ts` +**Description**: Simplify symbolic sums +**Interface**: +```typescript +symbolicSum(expr: Node | string, variable: string, lower: number, upper: number | string): Node +``` +**Handles**: Arithmetic/geometric series, common summation identities + +#### Task 15.5: Implement Equation Solver (solve) +**File**: `src/function/algebra/solve.ts` +**Description**: Solve equations symbolically +**Interface**: +```typescript +solve(equation: Node | string, variable: string): Node[] // All solutions +solveSystem(equations: Array, variables: string[]): Object[] +``` +**Scope**: Linear, quadratic, polynomial, some transcendental + +#### Task 15.6: Implement Polynomial Factorization +**File**: `src/function/algebra/factor.ts` +**Description**: Factor polynomials +**Interface**: +```typescript +factor(expr: Node | string): Node +factorList(expr: Node | string): Array<{ factor: Node, exponent: number }> +``` +**Algorithm**: Integer factorization, rational root theorem + +#### Task 15.7: Implement Partial Fractions +**File**: `src/function/algebra/partialFractions.ts` +**Description**: Partial fraction decomposition +**Interface**: +```typescript +partialFractions(expr: Node | string, variable: string): Node +``` +**Use**: Integration of rational functions + +#### Task 15.8: Implement Expression Expansion +**File**: `src/function/algebra/expand.ts` +**Description**: Expand expressions (opposite of factor) +**Interface**: +```typescript +expand(expr: Node | string): Node +expandAll(expr: Node | string): Node // Recursive expansion +``` + +--- + +## Implementation Guidelines + +### Code Standards + +1. **TypeScript First**: All new code should be in TypeScript +2. **Factory Pattern**: Use mathjs factory pattern for all functions +3. **Typed Functions**: Support multiple numeric types (number, BigNumber, Complex, Matrix) +4. **Pure Functions**: Implement core algorithms as pure functions in `src/plain/` +5. **WASM**: Add WASM implementations for computationally intensive functions + +### File Structure Template + +```typescript +// src/function/category/functionName.ts + +import { factory } from '../../utils/factory.js' +import { functionNameNumber } from '../../plain/number/functionName.js' + +const name = 'functionName' +const dependencies = ['typed', 'config', /* other deps */] + +export const createFunctionName = factory(name, dependencies, ({ typed, config }) => { + /** + * Description of the function. + * + * Syntax: + * math.functionName(param1, param2) + * + * Examples: + * math.functionName(1, 2) // returns X + * + * @param {number | BigNumber | Complex | Matrix} param1 Description + * @param {number} param2 Description + * @return {number | BigNumber | Complex | Matrix} Description + */ + return typed(name, { + 'number': functionNameNumber, + 'BigNumber': (x) => /* BigNumber implementation */, + 'Complex': (x) => /* Complex implementation */, + 'Array | Matrix': typed.referToSelf(self => (x) => deepMap(x, self, true)) + }) +}) +``` + +### Testing Template + +```javascript +// test/unit-tests/function/category/functionName.test.js + +import assert from 'assert' +import math from '../../../../src/defaultInstance.js' + +const { functionName, matrix, complex, bignumber } = math + +describe('functionName', function () { + it('should compute correct result for numbers', function () { + assert.strictEqual(functionName(1, 2), expectedResult) + }) + + it('should work with matrices', function () { + const result = functionName(matrix([[1, 2], [3, 4]])) + assert.deepStrictEqual(result.toArray(), [[...], [...]]) + }) + + it('should throw for invalid input', function () { + assert.throws(function () { functionName('invalid') }, /TypeError/) + }) +}) +``` + +### WASM Template + +```typescript +// src-wasm/category/functionName.ts + +export function functionName(x: f64): f64 { + // High-performance implementation + return result +} + +// For array operations +export function functionNameArray( + input: Float64Array, + output: Float64Array, + length: i32 +): void { + for (let i: i32 = 0; i < length; i++) { + output[i] = functionName(input[i]) + } +} +``` + +--- + +## Priority Matrix + +| Sprint | Functions Added | Cumulative | Scientific Computing % | +|--------|----------------|------------|------------------------| +| Current | 214 | 214 | 35% | +| Sprint 1 | 8 | 222 | 40% | +| Sprint 2 | 10 | 232 | 45% | +| Sprint 3 | 10 | 242 | 50% | +| Sprint 4 | 10 | 252 | 55% | +| Sprint 5 | 8 | 260 | 60% | +| Sprint 6 | 8 | 268 | 65% | +| Sprint 7 | 8 | 276 | 70% | +| Sprint 8 | 10 | 286 | 75% | +| Sprint 9 | 10 | 296 | 80% | +| Sprint 10 | 10 | 306 | 83% | +| Sprint 11 | 8 | 314 | 86% | +| Sprint 12 | 8 | 322 | 89% | +| Sprint 13 | 8 | 330 | 92% | +| Sprint 14 | 10 | 340 | 95% | +| Sprint 15 | 8 | 348 | 97% | + +--- + +## Success Criteria + +### Per-Sprint Completion Requirements + +1. **All tasks implemented** with TypeScript source files +2. **Unit tests** achieving >90% code coverage +3. **Documentation** in JSDoc format +4. **Type definitions** updated in `types/index.d.ts` +5. **Factory registration** in `src/factoriesAny.js` +6. **Performance benchmarks** for WASM-accelerated functions + +### Overall Project Success + +1. **Scientific Computing Parity**: 95%+ feature coverage vs SciPy +2. **Performance**: WASM acceleration for matrix operations and special functions +3. **Type Safety**: Full TypeScript coverage +4. **Test Coverage**: 90%+ overall +5. **Documentation**: Complete API documentation for all new functions + +--- + +## Appendix: Function Reference by Domain + +### A. Linear Algebra (Sprint 1, 12) +- SVD, Cholesky, polar, rank, nullspace, orth, lstsq +- BVP solvers, DAE solvers + +### B. Special Functions (Sprints 2, 8, 13, 14) +- Bessel: j0, j1, jn, y0, y1, yn, i0, i1, in, k0, k1, kn, hankel1, hankel2 +- Airy: ai, aip, bi, bip +- Elliptic: ellipk, ellipe, ellipkinc, ellipeinc, ellippi, jacobi sn/cn/dn +- Hypergeometric: hyp1f1, hyp2f1, hypU, hyppfq +- Orthogonal: legendre, hermite, laguerre, chebyshev, jacobi, gegenbauer +- Other: digamma, polygamma, lambertw, expint, fresnel, struve, polylog + +### C. Probability & Statistics (Sprints 3, 4, 9) +- Distributions: normal, t, chi2, F, beta, gamma, exponential, weibull, lognormal, poisson, binomial, etc. +- Tests: ttest, chi2test, ftest, kstest, anova +- Descriptive: skewness, kurtosis, cov, corrcoef, kde, bootstrap + +### D. Numerical Methods (Sprints 5, 6, 7) +- Root finding: brentq, newton, fzero, fsolve +- Optimization: goldenSection, fminbnd, nelderMead, bfgs +- Integration: trapz, simps, quad, romberg, gausslegendre, dblquad +- Interpolation: interp1, spline, pchip, interp2, polyfit, curvefit + +### E. Signal Processing (Sprints 10, 11) +- Filters: butter, cheby1, cheby2, ellip, fir1, filter, filtfilt +- Windows: hamming, hanning, blackman, kaiser, bartlett +- Spectral: periodogram, pwelch, stft, conv, xcorr + +### F. Differential Equations (Sprint 12) +- ODE: BDF, Adams-Bashforth, event detection +- DAE, BVP, DDE solvers +- PDE: pdepe, method of lines + +### G. Symbolic (Sprint 15) +- integrate, limit, taylor, symbolicSum, solve, factor, partialFractions, expand + +--- + +*Document Version: 1.0* +*Created: 2025-11-28* +*Target Completion: 15 sprints (150 days with 10 days/sprint)*