UNPKG

@railpath/finance-toolkit

Version:

Production-ready finance library for portfolio construction, risk analytics, quantitative metrics, and ML-based regime detection

291 lines (290 loc) 10 kB
"use strict"; /** * Constraint Projection Utilities * * Collection of utility functions for projecting solutions onto constraint sets, * commonly used in mathematical optimization and portfolio analysis. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.projectOntoEqualityConstraints = projectOntoEqualityConstraints; exports.projectOntoNonNegativityConstraints = projectOntoNonNegativityConstraints; exports.projectOntoBoxConstraints = projectOntoBoxConstraints; exports.projectOntoSimplex = projectOntoSimplex; exports.projectGradientOntoEqualityConstraints = projectGradientOntoEqualityConstraints; exports.projectGradientOntoNonNegativityConstraints = projectGradientOntoNonNegativityConstraints; exports.calculateEqualityConstraintViolation = calculateEqualityConstraintViolation; exports.calculateInequalityConstraintViolation = calculateInequalityConstraintViolation; exports.isSolutionFeasible = isSolutionFeasible; const matrixOperations_1 = require("./matrixOperations"); const vectorOperations_1 = require("./vectorOperations"); const linearSystemSolver_1 = require("./linearSystemSolver"); /** * Project a solution vector onto equality constraints Ax = b * * @param x - Current solution vector * @param A - Constraint matrix (m×n) * @param b - Right-hand side vector (m×1) * @returns Projected solution vector * * @example * ```typescript * const x = [0.5, 0.3, 0.2]; * const A = [[1, 1, 1]]; * const b = [1]; * const projected = projectOntoEqualityConstraints(x, A, b); * ``` */ function projectOntoEqualityConstraints(x, A, b) { const m = A.length; if (m === 0) { return x; } // Handle simple case: sum constraint if (m === 1 && A[0].every(val => Math.abs(val - 1) < 1e-12)) { // Constraint: sum(x) = b[0] const currentSum = x.reduce((sum, val) => sum + val, 0); const targetSum = b[0]; if (Math.abs(currentSum) > 1e-12) { const scale = targetSum / currentSum; return x.map(val => val * scale); } } // For other constraints, use iterative projection let result = [...x]; const maxIter = 10; for (let iter = 0; iter < maxIter; iter++) { let maxViolation = 0; for (let i = 0; i < m; i++) { const constraintValue = (0, vectorOperations_1.vectorDot)(A[i], result); const violation = constraintValue - b[i]; if (Math.abs(violation) > Math.abs(maxViolation)) { maxViolation = violation; } // Simple projection: adjust all variables proportionally const constraintNorm = (0, vectorOperations_1.vectorDot)(A[i], A[i]); if (constraintNorm > 1e-12) { const adjustment = violation / constraintNorm; result = (0, vectorOperations_1.vectorSubtract)(result, (0, vectorOperations_1.vectorScale)(A[i], adjustment)); } } if (Math.abs(maxViolation) < 1e-8) { break; } } return result; } /** * Project a solution vector onto non-negativity constraints x ≥ 0 * * @param x - Current solution vector * @returns Projected solution vector with non-negative elements * * @example * ```typescript * const x = [-0.1, 0.5, -0.2]; * const projected = projectOntoNonNegativityConstraints(x); // [0, 0.5, 0] * ``` */ function projectOntoNonNegativityConstraints(x) { return x.map(val => Math.max(0, val)); } /** * Project a solution vector onto box constraints l ≤ x ≤ u * * @param x - Current solution vector * @param lowerBounds - Lower bounds (n×1) * @param upperBounds - Upper bounds (n×1) * @returns Projected solution vector within bounds * * @example * ```typescript * const x = [0.1, 0.8, 0.05]; * const lower = [0.1, 0.1, 0.1]; * const upper = [0.5, 0.5, 0.5]; * const projected = projectOntoBoxConstraints(x, lower, upper); * ``` */ function projectOntoBoxConstraints(x, lowerBounds, upperBounds) { if (x.length !== lowerBounds.length || x.length !== upperBounds.length) { throw new Error('All vectors must have the same length'); } return x.map((val, i) => { return Math.max(lowerBounds[i], Math.min(upperBounds[i], val)); }); } /** * Project a solution vector onto the unit simplex (x ≥ 0, sum(x) = 1) * * @param x - Current solution vector * @returns Projected solution vector on unit simplex * * @example * ```typescript * const x = [0.5, 0.3, 0.4]; * const projected = projectOntoSimplex(x); // [0.416, 0.25, 0.333] * ``` */ function projectOntoSimplex(x) { // First project onto non-negativity constraints let result = projectOntoNonNegativityConstraints(x); // Then project onto sum constraint const currentSum = result.reduce((sum, val) => sum + val, 0); if (Math.abs(currentSum) > 1e-12) { const scale = 1 / currentSum; result = result.map(val => val * scale); } return result; } /** * Project a gradient onto the null space of equality constraints * * @param gradient - Gradient vector * @param A - Constraint matrix (m×n) * @returns Projected gradient * * @example * ```typescript * const gradient = [1, 2, 3]; * const A = [[1, 1, 1]]; * const projected = projectGradientOntoEqualityConstraints(gradient, A); * ``` */ function projectGradientOntoEqualityConstraints(gradient, A) { if (A.length === 0) { return gradient; } // Project gradient onto null space of A // P = I - Aᵀ(AAᵀ)⁻¹A const At = (0, matrixOperations_1.matrixTranspose)(A); const AAt = (0, matrixOperations_1.matrixMatrixMultiply)(A, At); try { // Solve AAt * y = A * gradient const Ag = (0, matrixOperations_1.matrixVectorMultiply)(A, gradient); const y = (0, linearSystemSolver_1.solveLinearSystem)(AAt, Ag); // Calculate projection: gradient - Aᵀ * y const At_y = (0, matrixOperations_1.matrixVectorMultiply)(At, y); return (0, vectorOperations_1.vectorSubtract)(gradient, At_y); } catch { // If solving fails, return original gradient return gradient; } } /** * Project a gradient onto non-negativity constraints * * @param gradient - Gradient vector * @param x - Current solution vector * @returns Projected gradient respecting non-negativity constraints * * @example * ```typescript * const gradient = [1, -2, 3]; * const x = [0.1, 0, 0.5]; * const projected = projectGradientOntoNonNegativityConstraints(gradient, x); * ``` */ function projectGradientOntoNonNegativityConstraints(gradient, x) { if (gradient.length !== x.length) { throw new Error('Gradient and solution vectors must have the same length'); } return gradient.map((g, i) => { // If x[i] is at boundary (x[i] = 0) and gradient points inward, set to 0 if (x[i] <= 1e-12 && g < 0) { return 0; } return g; }); } /** * Calculate constraint violation for equality constraints * * @param x - Solution vector * @param A - Constraint matrix (m×n) * @param b - Right-hand side vector (m×1) * @returns Maximum constraint violation * * @example * ```typescript * const x = [0.5, 0.3, 0.4]; * const A = [[1, 1, 1]]; * const b = [1]; * const violation = calculateEqualityConstraintViolation(x, A, b); * ``` */ function calculateEqualityConstraintViolation(x, A, b) { if (A.length === 0) { return 0; } let maxViolation = 0; for (let i = 0; i < A.length; i++) { const constraintValue = (0, vectorOperations_1.vectorDot)(A[i], x); const violation = Math.abs(constraintValue - b[i]); maxViolation = Math.max(maxViolation, violation); } return maxViolation; } /** * Calculate constraint violation for inequality constraints * * @param x - Solution vector * @param G - Inequality constraint matrix (m×n) * @param h - Right-hand side vector (m×1) * @returns Maximum constraint violation (positive if violated) * * @example * ```typescript * const x = [0.5, 0.3]; * const G = [[1, 0], [0, 1]]; // x ≥ 0 * const h = [0, 0]; * const violation = calculateInequalityConstraintViolation(x, G, h); * ``` */ function calculateInequalityConstraintViolation(x, G, h) { if (G.length === 0) { return 0; } let maxViolation = 0; for (let i = 0; i < G.length; i++) { const constraintValue = (0, vectorOperations_1.vectorDot)(G[i], x); // For inequality constraints Gx ≤ h, violation = max(0, constraintValue - h[i]) // But for x ≥ 0, we want -x ≤ 0, so G = [-1, 0, 0], h = [0] const violation = Math.max(0, constraintValue - h[i]); maxViolation = Math.max(maxViolation, violation); } return maxViolation; } /** * Check if a solution is feasible with respect to constraints * * @param x - Solution vector * @param equalityConstraints - Equality constraints {A, b} * @param inequalityConstraints - Inequality constraints {G, h} * @param tolerance - Feasibility tolerance (default: 1e-6) * @returns True if solution is feasible * * @example * ```typescript * const x = [0.3, 0.3, 0.4]; * const eq = { A: [[1, 1, 1]], b: [1] }; * const ineq = { G: [[1, 0, 0], [0, 1, 0], [0, 0, 1]], h: [0, 0, 0] }; * const feasible = isSolutionFeasible(x, eq, ineq); * ``` */ function isSolutionFeasible(x, equalityConstraints, inequalityConstraints, tolerance = 1e-6) { // Check equality constraints if (equalityConstraints) { const eqViolation = calculateEqualityConstraintViolation(x, equalityConstraints.A, equalityConstraints.b); if (eqViolation > tolerance) { return false; } } // Check inequality constraints if (inequalityConstraints) { const ineqViolation = calculateInequalityConstraintViolation(x, inequalityConstraints.G, inequalityConstraints.h); if (ineqViolation > tolerance) { return false; } } return true; }