From 8fc3af02649f8577352ded69ee3b4ea3734e0062 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Tue, 17 Mar 2020 07:11:25 -0400 Subject: [PATCH] modified linear solvers to handle matrix RHS, added error checking to lerp and affine_frame_map, adde same_shape(), added square option to is_matrix. --- affine.scad | 14 ++++++++---- common.scad | 11 ++++++++++ math.scad | 61 ++++++++++++++++++++++++++++++++++++++--------------- 3 files changed, 65 insertions(+), 21 deletions(-) diff --git a/affine.scad b/affine.scad index 5f0b248..4764de5 100644 --- a/affine.scad +++ b/affine.scad @@ -245,13 +245,14 @@ function affine3d_rot_from_to(from, to) = let( // Description: // Returns a transformation that maps one coordinate frame to another. You must specify two or three of `x`, `y`, and `z`. The specified // axes are mapped to the vectors you supplied. If you give two inputs, the third vector is mapped to the appropriate normal to maintain a right hand coordinate system. -// If the vectors you give are orthogonal the result will be a rotation. The `reverse` parameter will supply the inverse map, which enables you -// to map two arbitrary coordinate systems two each other by using the canonical coordinate system as an intermediary. +// If the vectors you give are orthogonal the result will be a rotation and the `reverse` parameter will supply the inverse map, which enables you +// to map two arbitrary coordinate systems two each other by using the canonical coordinate system as an intermediary. You cannot use the `reverse` option +// with non-orthogonal inputs. // Arguments: // x = Destination vector for x axis // y = Destination vector for y axis // z = Destination vector for z axis -// reverse = reverse direction of the map. Default: false +// reverse = reverse direction of the map for orthogonal inputs. Default: false // Examples: // T = affine_frame_map(x=[1,1,0], y=[-1,1]); // This map is just a rotation around the z axis // T = affine_frame_map(x=[1,0,0], y=[1,1]); // This map is not a rotation because x and y aren't orthogonal @@ -276,7 +277,12 @@ function affine_frame_map(x,y,z, reverse=false) = is_undef(z) ? [x, y, cross(x,y)] : [x, y, z] ) - reverse ? affine2d_to_3d(map) : affine2d_to_3d(transpose(map)); + reverse ? + let( ocheck = approx(map[0]*map[1],0) && approx(map[0]*map[2],0) && approx(map[1]*map[2],0)) + assert(ocheck, "Inputs must be orthogonal when reverse==true") + affine2d_to_3d(map) + : + affine2d_to_3d(transpose(map)); diff --git a/common.scad b/common.scad index 5d0a3c0..3467d1a 100644 --- a/common.scad +++ b/common.scad @@ -116,6 +116,17 @@ function is_consistent(list) = is_list(list) && is_list_of(list, list[0]); +// Function: same_shape() +// Usage: +// same_shape(a,b) +// Description: +// Tests whether the inputs `a` and `b` are both numeric and are the same shaped list. +// Example: +// same_shape([3,[4,5]],[7,[3,4]]); // Returns true +// same_shape([3,4,5], [7,[3,4]]); // Returns false +function same_shape(a,b) = a*0 == b*0; + + // Section: Handling `undef`s. diff --git a/math.scad b/math.scad index e6cee05..5bce93e 100644 --- a/math.scad +++ b/math.scad @@ -106,7 +106,9 @@ function factorial(n,d=1) = product([for (i=[n:-1:d]) i]); // // Points colored in ROYGBIV order. // rainbow(pts) translate($item) circle(d=3,$fn=8); function lerp(a,b,u) = + assert(same_shape(a,b), "Bad or inconsistent inputs to lerp") is_num(u)? (1-u)*a + u*b : + assert(is_vector(u), "Input u to lerp must be number or vector") [for (v = u) lerp(a,b,v)]; @@ -536,15 +538,17 @@ function mean(v) = sum(v)/len(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 A is rank deficient or singular then linear_solve returns `undef`. 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. function linear_solve(A,b) = assert(is_matrix(A)) - assert(is_vector(b)) - let( - dim = array_dim(A), - m=dim[0], n=dim[1] - ) - assert(len(b)==m,str("Incompatible matrix and vector",dim,len(b))) + let( + m = len(A), + n = len(A[0]) + ) + assert(is_vector(b,m) || is_matrix(b,m),"Incompatible matrix and right hand side") let ( qr = m0 && (is_undef(m) || len(A)==m) && is_vector(A[0]) && (is_undef(n) || len(A[0])==n) && + (!square || n==m) && is_consistent(A); + // Section: Comparisons and Logic // Function: approx()