From 3864f14333c36f2e91f99b6037b3b455def2b215 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 11 Jul 2020 12:22:59 -0400 Subject: [PATCH] Added error bounds to poly_roots, added poly_div, poly_mult and poly_add, fixed bugs in factorial. --- math.scad | 147 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 124 insertions(+), 23 deletions(-) diff --git a/math.scad b/math.scad index 09e3b9c..7629cb1 100644 --- a/math.scad +++ b/math.scad @@ -67,7 +67,7 @@ function hypot(x,y,z=0) = norm([x,y,z]); // Usage: // x = factorial(n,[d]); // Description: -// Returns the factorial of the given integer value. +// Returns the factorial of the given integer value, or n!/d! if d is given. // Arguments: // n = The integer number to get the factorial of. (n!) // d = If given, the returned value will be (n! / d!) @@ -75,7 +75,10 @@ function hypot(x,y,z=0) = norm([x,y,z]); // x = factorial(4); // Returns: 24 // y = factorial(6); // Returns: 720 // z = factorial(9); // Returns: 362880 -function factorial(n,d=1) = product([for (i=[n:-1:d]) i]); +function factorial(n,d=0) = + assert(n>=0 && d>=0, "Factorial is not defined for negative numbers") + assert(d<=n, "d cannot be larger than n") + product([1,for (i=[n:-1:d+1]) i]); // Function: lerp() @@ -525,6 +528,17 @@ function deltas(v) = [for (p=pair(v)) p.y-p.x]; function product(v, i=0, tot=undef) = i>=len(v)? tot : product(v, i+1, ((tot==undef)? v[i] : is_vector(v[i])? vmul(tot,v[i]) : tot*v[i])); +// Function: outer_product() +// Description: +// Compute the outer product of two vectors, a matrix. +// Usage: +// M = outer_product(u,v); +function outer_product(u,v) = + assert(is_vector(u) && is_vector(v)) + assert(len(u)==len(v)) + [for(i=[0:len(u)-1]) [for(j=[0:len(u)-1]) u[i]*v[j]]]; + + // Function: mean() // Description: // Returns the arithmatic mean/average of all entries in the given array. @@ -563,7 +577,7 @@ function median(v) = // Description: // Solves the linear system Ax=b. If A is square and non-singular the unique solution is returned. If A is overdetermined // the least squares solution is returned. If A is underdetermined, the minimal norm solution is returned. -// If A is rank deficient or singular then linear_solve returns `undef`. If b is a matrix that is compatible with A +// If A is rank deficient or singular then linear_solve returns []. If b is a matrix that is compatible with A // then the problem is solved for the matrix valued right hand side and a matrix is returned. Note that if you // want to solve Ax=b1 and Ax=b2 that you need to form the matrix transpose([b1,b2]) for the right hand side and then // transpose the returned value. @@ -582,7 +596,7 @@ function linear_solve(A,b) = R = submatrix(qr[1],[0:mindim-1], [0:mindim-1]), zeros = [for(i=[0:mindim-1]) if (approx(R[i][i],0)) i] ) - zeros != [] ? undef : + zeros != [] ? [] : m=0 && j0 && d[0]!=0 , "Denominator is zero or has leading zero coefficient") + len(n)qlen ? p : q, + short = plen>qlen ? q : p + ) + _poly_trim(long + concat(repeat(0,len(long)-len(short)),short)); + + // Function: poly_roots() // Usage: // poly_roots(p,[tol]) @@ -1062,14 +1151,18 @@ function polynomial(p, z, k, zk, total) = // Dario Bini. "Numerical computation of polynomial zeros by means of Aberth's Method", Numerical Algorithms, Feb 1996. // https://www.researchgate.net/publication/225654837_Numerical_computation_of_polynomial_zeros_by_means_of_Aberth's_method -function poly_roots(p,tol=1e-14) = +function poly_roots(p,tol=1e-14,error_bound=false) = assert(p!=[], "Input polynomial must have a nonzero coefficient") assert(is_vector(p), "Input must be a vector") - p[0] == 0 ? poly_roots(slice(p,1,-1)) : // Strip leading zero coefficients - p[len(p)-1] == 0 ? [[0,0], // Strip trailing zero coefficients - each poly_roots(select(p,0,-2))] : - len(p)==1 ? [] : // Nonzero constant case has no solutions - len(p)==2 ? [[-p[1]/p[0],0]] : // Linear case needs special handling + p[0] == 0 ? poly_roots(slice(p,1,-1),tol=tol,error_bound=error_bound) : // Strip leading zero coefficients + p[len(p)-1] == 0 ? // Strip trailing zero coefficients + let( solutions = poly_roots(select(p,0,-2),tol=tol, error_bound=error_bound)) + (error_bound ? [ [[0,0], each solutions[0]], [0, each solutions[1]]] + : [[0,0], each solutions]) : + len(p)==1 ? (error_bound ? [[],[]] : []) : // Nonzero constant case has no solutions + len(p)==2 ? let( solution = [[-p[1]/p[0],0]]) // Linear case needs special handling + (error_bound ? [solution,[0]] : solution) + : let( n = len(p)-1, // polynomial degree pderiv = [for(i=[0:n-1]) p[i]*(n-i)], @@ -1082,9 +1175,12 @@ function poly_roots(p,tol=1e-14) = init = [for(i=[0:1:n-1]) // Initial guess for roots let(angle = 360*i/n+270/n/PI) [beta,0]+r*[cos(angle),sin(angle)] - ] + ], + roots = _poly_roots(p,pderiv,s,init,tol=tol), + error = error_bound ? [for(xi=roots) n * (norm(polynomial(p,xi))+tol*polynomial(s,norm(xi))) / + abs(norm(polynomial(pderiv,xi))-tol*polynomial(s,norm(xi)))] : 0 ) - _poly_roots(p,pderiv,s,init,tol=tol); + error_bound ? [roots, error] : roots; // p = polynomial // pderiv = derivative polynomial of p @@ -1114,7 +1210,10 @@ function _poly_roots(p, pderiv, s, z, tol, i=0) = // The polynomial is specified as p=[a_n, a_{n-1},...,a_1,a_0] // where a_n is the x^n coefficient. This function works by // computing the complex roots and returning those roots where -// the imaginary part is closed to zero, specifically: z.y/(1+norm(z)) < eps. Because +// the imaginary part is closed to zero. By default it uses a computed +// error bound from the polynomial solver to decide whether imaginary +// parts are zero. You can specify eps, in which case the test is +// z.y/(1+norm(z)) < eps. Because // of poor convergence and higher error for repeated roots, such roots may // be missed by the algorithm because their imaginary part is large. // Arguments: @@ -1122,11 +1221,13 @@ function _poly_roots(p, pderiv, s, z, tol, i=0) = // eps = used to determine whether imaginary parts of roots are zero // tol = tolerance for the complex polynomial root finder -function real_roots(p,eps=EPSILON,tol=1e-14) = +function real_roots(p,eps=undef,tol=1e-14) = let( - roots = poly_roots(p) + roots_err = poly_roots(p,error_bound=true), + roots = roots_err[0], + err = roots_err[1] ) - [for(z=roots) if (abs(z.y)/(1+norm(z))