diff --git a/.github/workflows/docsgen.yml b/.github/workflows/docsgen.yml index 8ab135c..911fac2 100644 --- a/.github/workflows/docsgen.yml +++ b/.github/workflows/docsgen.yml @@ -41,6 +41,11 @@ jobs: cd $GITHUB_WORKSPACE ./scripts/genindex.sh + - name: Generate Cheat Sheet + run: | + cd $GITHUB_WORKSPACE + ./scripts/gencheat.sh + - name: Generating Docs env: GH_PAT: ${{ secrets.GH_PAT }} diff --git a/arrays.scad b/arrays.scad index e2d3ac2..37fee58 100644 --- a/arrays.scad +++ b/arrays.scad @@ -1199,6 +1199,55 @@ function zip(vecs, v2, v3, fit=false, fill=undef) = : [for(i=[0:1:minlen-1]) [for(v=vecs) for(x=v[i]) x] ]; +// Function: block_matrix() +// Usage: +// block_matrix([[M11, M12,...],[M21, M22,...], ... ]) +// Description: +// Create a block matrix by supplying a matrix of matrices, which will +// be combined into one unified matrix. Every matrix in one row +// must have the same height, and the combined width of the matrices +// in each row must be equal. +function block_matrix(M) = + let( + bigM = [for(bigrow = M) each zip(bigrow)], + len0=len(bigM[0]), + badrows = [for(row=bigM) if (len(row)!=len0) 1] + ) + assert(badrows==[], "Inconsistent or invalid input") + bigM; + +// Function: diagonal_matrix() +// Usage: +// diagonal_matrix(diag, [offdiag]) +// Description: +// Creates a square matrix with the items in the list `diag` on +// its diagonal. The off diagonal entries are set to offdiag, +// which is zero by default. +function diagonal_matrix(diag,offdiag=0) = + assert(is_list(diag) && len(diag)>0) + [for(i=[0:1:len(diag)-1]) [for(j=[0:len(diag)-1]) i==j?diag[i] : offdiag]]; + + +// Function: submatrix_set() +// Usage: submatrix_set(M,A,[m],[n]) +// Description: +// Sets a submatrix of M equal to the matrix A. By default the top left corner of M is set to A, but +// you can specify offset coordinates m and n. If A (as adjusted by m and n) extends beyond the bounds +// of M then the extra entries are ignored. You can pass in A=[[]], a null matrix, and M will be +// returned unchanged. Note that the input M need not be rectangular in shape. +function submatrix_set(M,A,m=0,n=0) = + assert(is_list(M)) + assert(is_list(A)) + assert(is_int(m)) + assert(is_int(n)) + let( badrows = [for(i=idx(A)) if (!is_list(A[i])) i]) + assert(badrows==[], str("Input submatrix malformed rows: ",badrows)) + [for(i=[0:1:len(M)-1]) + assert(is_list(M[i]), str("Row ",i," of input matrix is not a list")) + [for(j=[0:1:len(M[i])-1]) + i>=m && i =n && jj ? 0 : ri[j] ] ] - ) [qr[0],Rzero]; + ) [qr[0],Rzero,qr[2]]; -function _qr_factor(A,Q, column, m, n) = - column >= min(m-1,n) ? [Q,A] : +function _qr_factor(A,Q,P, pivot, column, m, n) = + column >= min(m-1,n) ? [Q,A,P] : let( + swap = !pivot ? 1 + : _swap_matrix(n,column,column+max_index([for(i=[column:n-1]) sum_of_squares([for(j=[column:m-1]) A[j][i]])])), + A = pivot ? A*swap : A, x = [for(i=[column:1:m-1]) A[i][column]], alpha = (x[0]<=0 ? 1 : -1) * norm(x), u = x - concat([alpha],repeat(0,m-1)), v = alpha==0 ? u : u / norm(u), Qc = ident(len(x)) - 2*outer_product(v,v), - Qf = [for(i=[0:m-1]) - [for(j=[0:m-1]) - i=0 && j>=0, "Swap indices out of bounds") + [for(y=[0:n-1]) [for (x=[0:n-1]) + x==i ? (y==j ? 1 : 0) + : x==j ? (y==i ? 1 : 0) + : x==y ? 1 : 0]]; + // Function: back_substitute() @@ -862,122 +887,156 @@ function is_matrix(A,m,n,square=false) = && ( !square || len(A)==len(A[0])); +// Function: norm_fro() +// Usage: +// norm_fro(A) +// Description: +// Computes frobenius norm of input matrix. The frobenius norm is the square root of the sum of the +// squares of all of the entries of the matrix. On vectors it is the same as the usual 2-norm. +// This is an easily computed norm that is convenient for comparing two matrices. +function norm_fro(A) = + assert(is_matrix(A) || is_vector(A)) + norm(flatten(A)); + + // Section: Comparisons and Logic -// Function: is_zero() +// Function: all_zero() // Usage: -// is_zero(x); +// all_zero(x); // Description: -// Returns true if the number passed to it is approximately zero, to within `eps`. +// Returns true if the finite number passed to it is approximately zero, to within `eps`. // If passed a list, recursively checks if all items in the list are approximately zero. // Otherwise, returns false. // Arguments: // x = The value to check. // eps = The maximum allowed variance. Default: `EPSILON` (1e-9) // Example: -// is_zero(0); // Returns: true. -// is_zero(1e-3); // Returns: false. -// is_zero([0,0,0]); // Returns: true. -// is_zero([0,0,1e-3]); // Returns: false. -function is_zero(x, eps=EPSILON) = - is_list(x)? (x != [] && [for (xx=x) if(!is_zero(xx,eps=eps)) 1] == []) : - is_num(x)? approx(x,eps) : +// all_zero(0); // Returns: true. +// all_zero(1e-3); // Returns: false. +// all_zero([0,0,0]); // Returns: true. +// all_zero([0,0,1e-3]); // Returns: false. +function all_zero(x, eps=EPSILON) = + is_finite(x)? approx(x,eps) : + is_list(x)? (x != [] && [for (xx=x) if(!all_zero(xx,eps=eps)) 1] == []) : false; -// Function: is_positive() +// Function: all_nonzero() // Usage: -// is_positive(x); +// all_nonzero(x); // Description: -// Returns true if the number passed to it is greater than zero. +// Returns true if the finite number passed to it is not almost zero, to within `eps`. +// If passed a list, recursively checks if all items in the list are not almost zero. +// Otherwise, returns false. +// Arguments: +// x = The value to check. +// eps = The maximum allowed variance. Default: `EPSILON` (1e-9) +// Example: +// all_nonzero(0); // Returns: false. +// all_nonzero(1e-3); // Returns: true. +// all_nonzero([0,0,0]); // Returns: false. +// all_nonzero([0,0,1e-3]); // Returns: false. +// all_nonzero([1e-3,1e-3,1e-3]); // Returns: true. +function all_nonzero(x, eps=EPSILON) = + is_finite(x)? !approx(x,eps) : + is_list(x)? (x != [] && [for (xx=x) if(!all_nonzero(xx,eps=eps)) 1] == []) : + false; + + +// Function: all_positive() +// Usage: +// all_positive(x); +// Description: +// Returns true if the finite number passed to it is greater than zero. // If passed a list, recursively checks if all items in the list are positive. // Otherwise, returns false. // Arguments: // x = The value to check. // Example: -// is_positive(-2); // Returns: false. -// is_positive(0); // Returns: false. -// is_positive(2); // Returns: true. -// is_positive([0,0,0]); // Returns: false. -// is_positive([0,1,2]); // Returns: false. -// is_positive([3,1,2]); // Returns: true. -// is_positive([3,-1,2]); // Returns: false. -function is_positive(x) = - is_list(x)? (x != [] && [for (xx=x) if(!is_positive(xx)) 1] == []) : +// all_positive(-2); // Returns: false. +// all_positive(0); // Returns: false. +// all_positive(2); // Returns: true. +// all_positive([0,0,0]); // Returns: false. +// all_positive([0,1,2]); // Returns: false. +// all_positive([3,1,2]); // Returns: true. +// all_positive([3,-1,2]); // Returns: false. +function all_positive(x) = is_num(x)? x>0 : + is_list(x)? (x != [] && [for (xx=x) if(!all_positive(xx)) 1] == []) : false; -// Function: is_negative() +// Function: all_negative() // Usage: -// is_negative(x); +// all_negative(x); // Description: -// Returns true if the number passed to it is less than zero. +// Returns true if the finite number passed to it is less than zero. // If passed a list, recursively checks if all items in the list are negative. // Otherwise, returns false. // Arguments: // x = The value to check. // Example: -// is_negative(-2); // Returns: true. -// is_negative(0); // Returns: false. -// is_negative(2); // Returns: false. -// is_negative([0,0,0]); // Returns: false. -// is_negative([0,1,2]); // Returns: false. -// is_negative([3,1,2]); // Returns: false. -// is_negative([3,-1,2]); // Returns: false. -// is_negative([-3,-1,-2]); // Returns: true. -function is_negative(x) = - is_list(x)? (x != [] && [for (xx=x) if(!is_negative(xx)) 1] == []) : +// all_negative(-2); // Returns: true. +// all_negative(0); // Returns: false. +// all_negative(2); // Returns: false. +// all_negative([0,0,0]); // Returns: false. +// all_negative([0,1,2]); // Returns: false. +// all_negative([3,1,2]); // Returns: false. +// all_negative([3,-1,2]); // Returns: false. +// all_negative([-3,-1,-2]); // Returns: true. +function all_negative(x) = is_num(x)? x<0 : + is_list(x)? (x != [] && [for (xx=x) if(!all_negative(xx)) 1] == []) : false; -// Function: is_nonpositive() +// Function: all_nonpositive() // Usage: -// is_nonpositive(x); +// all_nonpositive(x); // Description: -// Returns true if the number passed to it is less than or equal to zero. +// Returns true if the finite number passed to it is less than or equal to zero. // If passed a list, recursively checks if all items in the list are nonpositive. // Otherwise, returns false. // Arguments: // x = The value to check. // Example: -// is_nonpositive(-2); // Returns: true. -// is_nonpositive(0); // Returns: true. -// is_nonpositive(2); // Returns: false. -// is_nonpositive([0,0,0]); // Returns: true. -// is_nonpositive([0,1,2]); // Returns: false. -// is_nonpositive([3,1,2]); // Returns: false. -// is_nonpositive([3,-1,2]); // Returns: false. -// is_nonpositive([-3,-1,-2]); // Returns: true. -function is_nonpositive(x) = - is_list(x)? (x != [] && [for (xx=x) if(!is_nonpositive(xx)) 1] == []) : +// all_nonpositive(-2); // Returns: true. +// all_nonpositive(0); // Returns: true. +// all_nonpositive(2); // Returns: false. +// all_nonpositive([0,0,0]); // Returns: true. +// all_nonpositive([0,1,2]); // Returns: false. +// all_nonpositive([3,1,2]); // Returns: false. +// all_nonpositive([3,-1,2]); // Returns: false. +// all_nonpositive([-3,-1,-2]); // Returns: true. +function all_nonpositive(x) = is_num(x)? x<=0 : + is_list(x)? (x != [] && [for (xx=x) if(!all_nonpositive(xx)) 1] == []) : false; -// Function: is_nonnegative() +// Function: all_nonnegative() // Usage: -// is_nonnegative(x); +// all_nonnegative(x); // Description: -// Returns true if the number passed to it is greater than or equal to zero. +// Returns true if the finite number passed to it is greater than or equal to zero. // If passed a list, recursively checks if all items in the list are nonnegative. // Otherwise, returns false. // Arguments: // x = The value to check. // Example: -// is_nonnegative(-2); // Returns: false. -// is_nonnegative(0); // Returns: true. -// is_nonnegative(2); // Returns: true. -// is_nonnegative([0,0,0]); // Returns: true. -// is_nonnegative([0,1,2]); // Returns: true. -// is_nonnegative([0,-1,-2]); // Returns: false. -// is_nonnegative([3,1,2]); // Returns: true. -// is_nonnegative([3,-1,2]); // Returns: false. -// is_nonnegative([-3,-1,-2]); // Returns: false. -function is_nonnegative(x) = - is_list(x)? (x != [] && [for (xx=x) if(!is_nonnegative(xx)) 1] == []) : +// all_nonnegative(-2); // Returns: false. +// all_nonnegative(0); // Returns: true. +// all_nonnegative(2); // Returns: true. +// all_nonnegative([0,0,0]); // Returns: true. +// all_nonnegative([0,1,2]); // Returns: true. +// all_nonnegative([0,-1,-2]); // Returns: false. +// all_nonnegative([3,1,2]); // Returns: true. +// all_nonnegative([3,-1,2]); // Returns: false. +// all_nonnegative([-3,-1,-2]); // Returns: false. +function all_nonnegative(x) = is_num(x)? x>=0 : + is_list(x)? (x != [] && [for (xx=x) if(!all_nonnegative(xx)) 1] == []) : false; @@ -1309,6 +1368,41 @@ function C_div(z1,z2) = // Section: Polynomials +// Function: quadratic_roots() +// Usage: +// roots = quadratic_roots(a,b,c,[real]) +// Description: +// Computes roots of the quadratic equation a*x^2+b*x+c==0, where the +// coefficients are real numbers. If real is true then returns only the +// real roots. Otherwise returns a pair of complex values. This method +// may be more reliable than the general root finder at distinguishing +// real roots from complex roots. + +// https://people.csail.mit.edu/bkph/articles/Quadratics.pdf + +function quadratic_roots(a,b,c,real=false) = + real ? [for(root = quadratic_roots(a,b,c,real=false)) if (root.y==0) root.x] + : + is_undef(b) && is_undef(c) && is_vector(a,3) ? quadratic_roots(a[0],a[1],a[2]) : + assert(is_num(a) && is_num(b) && is_num(c)) + assert(a!=0 || b!=0 || c!=0, "Quadratic must have a nonzero coefficient") + a==0 && b==0 ? [] : // No solutions + a==0 ? [[-c/b,0]] : + let( + descrim = b*b-4*a*c, + sqrt_des = sqrt(abs(descrim)) + ) + descrim < 0 ? // Complex case + [[-b, sqrt_des], + [-b, -sqrt_des]]/2/a : + b<0 ? // b positive + [[2*c/(-b+sqrt_des),0], + [(-b+sqrt_des)/a/2,0]] + : // b negative + [[(-b-sqrt_des)/2/a, 0], + [2*c/(-b-sqrt_des),0]]; + + // Function: polynomial() // Usage: // polynomial(p, z) diff --git a/scripts/gencheat.sh b/scripts/gencheat.sh index 6762f0a..e07a909 100755 --- a/scripts/gencheat.sh +++ b/scripts/gencheat.sh @@ -13,53 +13,55 @@ function lcase function columnize { - cols=4 + cols=$2 TMPFILE=$(mktemp -t $(basename $0).XXXXXX) || exit 1 cat >>$TMPFILE - totcnt=$(wc -l $TMPFILE | awk '{print $1}') - maxrows=$((($totcnt+$cols-1)/$cols)) - maxcols=$cols - if [[ $maxcols -gt $totcnt ]] ; then - maxcols=$totcnt - fi - cnt=0 - hdrln1="| $(ucase $1) " - hdrln2='|:-----' - n=1 - while [[ $n < $maxcols ]] ; do - hdrln1+=' |  ' - hdrln2+=' |:------' - n=$(($n+1)) - done - hdrln1+=' |' - hdrln2+=' |' - n=0 - while [[ $n < $maxrows ]] ; do - lines[$n]="" - n=$(($n+1)) - done - col=0 - while IFS= read -r line; do - if [[ $col != 0 ]] ; then - lines[$cnt]+=" | " + if [[ $(wc -l $TMPFILE | awk '{print $1}') -gt 1 ]] ; then + totcnt=$(wc -l $TMPFILE | awk '{print $1}') + maxrows=$((($totcnt+$cols-1)/$cols)) + maxcols=$cols + if [[ $maxcols -gt $totcnt ]] ; then + maxcols=$totcnt fi - lines[$cnt]+="$line" - cnt=$(($cnt+1)) - if [[ $cnt = $maxrows ]] ; then - cnt=0 - col=$(($col+1)) - fi - done <$TMPFILE - rm -f $TMPFILE + cnt=0 + hdrln1="| $(ucase $1) " + hdrln2='|:-----' + n=1 + while [[ $n -lt $maxcols ]] ; do + hdrln1+=' |  ' + hdrln2+=' |:------' + n=$(($n+1)) + done + hdrln1+=' |' + hdrln2+=' |' + n=0 + while [[ $n -lt $maxrows ]] ; do + lines[$n]="" + n=$(($n+1)) + done + col=0 + while IFS= read -r line; do + if [[ $col != 0 ]] ; then + lines[$cnt]+=" | " + fi + lines[$cnt]+="$line" + cnt=$(($cnt+1)) + if [[ $cnt = $maxrows ]] ; then + cnt=0 + col=$(($col+1)) + fi + done <$TMPFILE + rm -f $TMPFILE - echo - echo $hdrln1 - echo $hdrln2 - n=0 - while [[ $n < $maxrows ]] ; do - echo "| ${lines[$n]} |" - n=$(($n+1)) - done + echo + echo $hdrln1 + echo $hdrln2 + n=0 + while [[ $n -lt $maxrows ]] ; do + echo "| ${lines[$n]} |" + n=$(($n+1)) + done + fi } function mkconstindex @@ -67,24 +69,29 @@ function mkconstindex sed 's/([^)]*)//g' | sed 's/[^a-zA-Z0-9_.:$]//g' | awk -F ':' '{printf "[%s](%s#%s)\n", $3, $1, $3}' } +function mkconstindex2 +{ + sed 's/ *=.*$//' | sed 's/[^a-zA-Z0-9_.:$]//g' | awk -F ':' '{printf "[%s](%s#%s)\n", $2, $1, $2}' +} + function mkotherindex { sed 's/([^)]*)//g' | sed 's/[^a-zA-Z0-9_.:$]//g' | awk -F ':' '{printf "[%s()](%s#%s)\n", $3, $1, $3}' } -CHEAT_FILES=$(grep '^include' std.scad | sed 's/^.*<\([a-zA-Z0-9.]*\)>/\1/'|grep -v 'version.scad') +CHEAT_FILES=$(grep '^include' std.scad | sed 's/^.*<\([a-zA-Z0-9.]*\)>/\1/' | grep -v 'version.scad' | grep -v 'primitives.scad') ( echo '## Belfry OpenScad Library Cheat Sheet' echo echo '( [Alphabetic Index](Index) )' echo + ( + grep -H '// Constant: ' $CHEAT_FILES | mkconstindex + grep -H '^[A-Z$][A-Z0-9_]* *=.*//' $CHEAT_FILES | mkconstindex2 + ) | sort -u | columnize 'Constants' 6 for f in $CHEAT_FILES ; do - #echo "### $f" - ( - egrep -H 'Constant: ' $f | mkconstindex - egrep -H 'Function: |Function&Module: |Module: ' $f | mkotherindex - ) | columnize $f + egrep -H '// Function: |// Function&Module: |// Module: ' $f | mkotherindex | columnize "[$f]($f)" 4 echo done ) > BOSL2.wiki/CheatSheet.md diff --git a/scripts/genindex.sh b/scripts/genindex.sh index 9d3e8cd..51a9075 100755 --- a/scripts/genindex.sh +++ b/scripts/genindex.sh @@ -6,16 +6,18 @@ function ucase echo "$1" | tr '[:lower:]' '[:upper:]' } + function lcase { echo "$1" | tr '[:upper:]' '[:lower:]' } -function mkindex + +function alphaindex { - TMPFILE=$(mktemp -t $(basename $0).XXXXXX) || exit 1 - sed 's/([^)]*)//g' | sed 's/[^a-zA-Z0-9_.:$]//g' | awk -F ':' '{printf "- [%s](%s#%s)\n", $3, $1, $3}' | sort -d -f -u >> $TMPFILE alpha="A B C D E F G H I J K L M N O P Q R S T U V W X Y Z" + TMPFILE=$(mktemp -t $(basename $0).XXXXXX) || exit 1 + sort -d -f >> $TMPFILE for a in $alpha; do echo -n "[$a](#$(lcase "$a")) " done @@ -33,13 +35,31 @@ function mkindex } +function constlist +{ + sed 's/([^)]*)//g' | sed 's/[^a-zA-Z0-9_.:$]//g' | awk -F ':' '{printf "- [%s](%s#%s) (in %s)\n", $3, $1, $3, $1}' +} + +function constlist2 +{ + sed 's/ *=.*$//' | sed 's/[^a-zA-Z0-9_.:$]//g' | awk -F ':' '{printf "- [%s](%s#%s) (in %s)\n", $2, $1, $2, $1}' +} + + +function funclist +{ + sed 's/([^)]*)//g' | sed 's/[^a-zA-Z0-9_.:$]//g' | awk -F ':' '{printf "- [%s()](%s#%s) (in %s)\n", $3, $1, $3, $1}' +} + + ( echo "## Belfry OpenScad Library Index" ( - grep 'Constant: ' *.scad - grep 'Function: ' *.scad - grep 'Function&Module: ' *.scad - grep 'Module: ' *.scad - ) | mkindex + ( + grep 'Constant: ' *.scad | constlist + grep '^[A-Z]* *=.*//' *.scad | constlist2 + ) | sort -u + egrep 'Function: |Function&Module: |Module: ' *.scad | sort -u | funclist + ) | sort | alphaindex ) > BOSL2.wiki/Index.md diff --git a/std.scad b/std.scad index 1c2e45e..685b6ab 100644 --- a/std.scad +++ b/std.scad @@ -12,21 +12,6 @@ assert(version_num()>=20190301, "BOSL2 requires OpenSCAD version 2019.03.01 or l include include -include -include -include -include -include -include - -include -include -include -include -include -include -include - include include include @@ -36,6 +21,19 @@ include include include include +include +include +include +include +include +include +include +include +include +include +include +include +include // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/tests/test_arrays.scad b/tests/test_arrays.scad index 45cd2cd..1dfe421 100644 --- a/tests/test_arrays.scad +++ b/tests/test_arrays.scad @@ -470,6 +470,40 @@ module test_zip() { } test_zip(); +module test_block_matrix() { + A = [[1,2],[3,4]]; + B = ident(2); + assert_equal(block_matrix([[A,B],[B,A],[A,B]]), [[1,2,1,0],[3,4,0,1],[1,0,1,2],[0,1,3,4],[1,2,1,0],[3,4,0,1]]); + assert_equal(block_matrix([[A,B],ident(4)]), [[1,2,1,0],[3,4,0,1],[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]); + text = [["a","b"],["c","d"]]; + assert_equal(block_matrix([[text,B]]), [["a","b",1,0],["c","d",0,1]]); +} +test_block_matrix(); + + +module test_diagonal_matrix() { + assert_equal(diagonal_matrix([1,2,3]), [[1,0,0],[0,2,0],[0,0,3]]); + assert_equal(diagonal_matrix([1,"c",2]), [[1,0,0],[0,"c",0],[0,0,2]]); + assert_equal(diagonal_matrix([1,"c",2],"X"), [[1,"X","X"],["X","c","X"],["X","X",2]]); + assert_equal(diagonal_matrix([[1,1],[2,2],[3,3]], [0,0]), [[ [1,1],[0,0],[0,0]], [[0,0],[2,2],[0,0]], [[0,0],[0,0],[3,3]]]); +} +test_diagonal_matrix(); + +module test_submatrix_set() { + test = [[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15], [16,17,18,19,20]]; + ragged = [[1,2,3,4,5],[6,7,8,9,10],[11,12], [16,17]]; + assert_equal(submatrix_set(test,[[9,8],[7,6]]), [[9,8,3,4,5],[7,6,8,9,10],[11,12,13,14,15], [16,17,18,19,20]]); + assert_equal(submatrix_set(test,[[9,7],[8,6]],1),[[1,2,3,4,5],[9,7,8,9,10],[8,6,13,14,15], [16,17,18,19,20]]); + assert_equal(submatrix_set(test,[[9,8],[7,6]],n=1), [[1,9,8,4,5],[6,7,6,9,10],[11,12,13,14,15], [16,17,18,19,20]]); + assert_equal(submatrix_set(test,[[9,8],[7,6]],1,2), [[1,2,3,4,5],[6,7,9,8,10],[11,12,7,6,15], [16,17,18,19,20]]); + assert_equal(submatrix_set(test,[[9,8],[7,6]],-1,-1), [[6,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15], [16,17,18,19,20]]); + assert_equal(submatrix_set(test,[[9,8],[7,6]],n=4), [[1,2,3,4,9],[6,7,8,9,7],[11,12,13,14,15], [16,17,18,19,20]]); + assert_equal(submatrix_set(test,[[9,8],[7,6]],7,7), [[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15], [16,17,18,19,20]]); + assert_equal(submatrix_set(ragged, [["a","b"],["c","d"]], 1, 1), [[1,2,3,4,5],[6,"a","b",9,10],[11,"c"], [16,17]]); + assert_equal(submatrix_set(test, [[]]), test); +} +test_submatrix_set(); + module test_array_group() { v = [1,2,3,4,5,6]; diff --git a/tests/test_math.scad b/tests/test_math.scad index c3eb601..5641416 100644 --- a/tests/test_math.scad +++ b/tests/test_math.scad @@ -100,104 +100,128 @@ module test_is_matrix() { test_is_matrix(); -module test_is_zero() { - assert(is_zero(0)); - assert(is_zero([0,0,0])); - assert(is_zero([[0,0,0],[0,0]])); - assert(is_zero([EPSILON/2,EPSILON/2,EPSILON/2])); - assert(!is_zero(1e-3)); - assert(!is_zero([0,0,1e-3])); - assert(!is_zero([EPSILON*10,0,0])); - assert(!is_zero([0,EPSILON*10,0])); - assert(!is_zero([0,0,EPSILON*10])); - assert(!is_zero(true)); - assert(!is_zero(false)); - assert(!is_zero(INF)); - assert(!is_zero(-INF)); - assert(!is_zero(NAN)); - assert(!is_zero("foo")); - assert(!is_zero([])); - assert(!is_zero([0:1:2])); +module test_all_zero() { + assert(all_zero(0)); + assert(all_zero([0,0,0])); + assert(all_zero([[0,0,0],[0,0]])); + assert(all_zero([EPSILON/2,EPSILON/2,EPSILON/2])); + assert(!all_zero(1e-3)); + assert(!all_zero([0,0,1e-3])); + assert(!all_zero([EPSILON*10,0,0])); + assert(!all_zero([0,EPSILON*10,0])); + assert(!all_zero([0,0,EPSILON*10])); + assert(!all_zero(true)); + assert(!all_zero(false)); + assert(!all_zero(INF)); + assert(!all_zero(-INF)); + assert(!all_zero(NAN)); + assert(!all_zero("foo")); + assert(!all_zero([])); + assert(!all_zero([0:1:2])); } -test_is_zero(); +test_all_zero(); -module test_is_positive() { - assert(!is_positive(-2)); - assert(!is_positive(0)); - assert(is_positive(2)); - assert(!is_positive([0,0,0])); - assert(!is_positive([0,1,2])); - assert(is_positive([3,1,2])); - assert(!is_positive([3,-1,2])); - assert(!is_positive([])); - assert(!is_positive(true)); - assert(!is_positive(false)); - assert(!is_positive("foo")); - assert(!is_positive([0:1:2])); +module test_all_nonzero() { + assert(!all_nonzero(0)); + assert(!all_nonzero([0,0,0])); + assert(!all_nonzero([[0,0,0],[0,0]])); + assert(!all_nonzero([EPSILON/2,EPSILON/2,EPSILON/2])); + assert(all_nonzero(1e-3)); + assert(!all_nonzero([0,0,1e-3])); + assert(!all_nonzero([EPSILON*10,0,0])); + assert(!all_nonzero([0,EPSILON*10,0])); + assert(!all_nonzero([0,0,EPSILON*10])); + assert(all_nonzero([1e-3,1e-3,1e-3])); + assert(all_nonzero([EPSILON*10,EPSILON*10,EPSILON*10])); + assert(!all_nonzero(true)); + assert(!all_nonzero(false)); + assert(!all_nonzero(INF)); + assert(!all_nonzero(-INF)); + assert(!all_nonzero(NAN)); + assert(!all_nonzero("foo")); + assert(!all_nonzero([])); + assert(!all_nonzero([0:1:2])); } -test_is_positive(); +test_all_nonzero(); -module test_is_negative() { - assert(is_negative(-2)); - assert(!is_negative(0)); - assert(!is_negative(2)); - assert(!is_negative([0,0,0])); - assert(!is_negative([0,1,2])); - assert(!is_negative([3,1,2])); - assert(!is_negative([3,-1,2])); - assert(is_negative([-3,-1,-2])); - assert(!is_negative([-3,1,-2])); - assert(is_negative([[-5,-7],[-3,-1,-2]])); - assert(!is_negative([[-5,-7],[-3,1,-2]])); - assert(!is_negative([])); - assert(!is_negative(true)); - assert(!is_negative(false)); - assert(!is_negative("foo")); - assert(!is_negative([0:1:2])); +module test_all_positive() { + assert(!all_positive(-2)); + assert(!all_positive(0)); + assert(all_positive(2)); + assert(!all_positive([0,0,0])); + assert(!all_positive([0,1,2])); + assert(all_positive([3,1,2])); + assert(!all_positive([3,-1,2])); + assert(!all_positive([])); + assert(!all_positive(true)); + assert(!all_positive(false)); + assert(!all_positive("foo")); + assert(!all_positive([0:1:2])); } -test_is_negative(); +test_all_positive(); -module test_is_nonpositive() { - assert(is_nonpositive(-2)); - assert(is_nonpositive(0)); - assert(!is_nonpositive(2)); - assert(is_nonpositive([0,0,0])); - assert(!is_nonpositive([0,1,2])); - assert(is_nonpositive([0,-1,-2])); - assert(!is_nonpositive([3,1,2])); - assert(!is_nonpositive([3,-1,2])); - assert(!is_nonpositive([])); - assert(!is_nonpositive(true)); - assert(!is_nonpositive(false)); - assert(!is_nonpositive("foo")); - assert(!is_nonpositive([0:1:2])); +module test_all_negative() { + assert(all_negative(-2)); + assert(!all_negative(0)); + assert(!all_negative(2)); + assert(!all_negative([0,0,0])); + assert(!all_negative([0,1,2])); + assert(!all_negative([3,1,2])); + assert(!all_negative([3,-1,2])); + assert(all_negative([-3,-1,-2])); + assert(!all_negative([-3,1,-2])); + assert(all_negative([[-5,-7],[-3,-1,-2]])); + assert(!all_negative([[-5,-7],[-3,1,-2]])); + assert(!all_negative([])); + assert(!all_negative(true)); + assert(!all_negative(false)); + assert(!all_negative("foo")); + assert(!all_negative([0:1:2])); } -test_is_nonpositive(); +test_all_negative(); -module test_is_nonnegative() { - assert(!is_nonnegative(-2)); - assert(is_nonnegative(0)); - assert(is_nonnegative(2)); - assert(is_nonnegative([0,0,0])); - assert(is_nonnegative([0,1,2])); - assert(is_nonnegative([3,1,2])); - assert(!is_nonnegative([3,-1,2])); - assert(!is_nonnegative([-3,-1,-2])); - assert(!is_nonnegative([[-5,-7],[-3,-1,-2]])); - assert(!is_nonnegative([[-5,-7],[-3,1,-2]])); - assert(!is_nonnegative([[5,7],[3,-1,2]])); - assert(is_nonnegative([[5,7],[3,1,2]])); - assert(!is_nonnegative([])); - assert(!is_nonnegative(true)); - assert(!is_nonnegative(false)); - assert(!is_nonnegative("foo")); - assert(!is_nonnegative([0:1:2])); +module test_all_nonpositive() { + assert(all_nonpositive(-2)); + assert(all_nonpositive(0)); + assert(!all_nonpositive(2)); + assert(all_nonpositive([0,0,0])); + assert(!all_nonpositive([0,1,2])); + assert(all_nonpositive([0,-1,-2])); + assert(!all_nonpositive([3,1,2])); + assert(!all_nonpositive([3,-1,2])); + assert(!all_nonpositive([])); + assert(!all_nonpositive(true)); + assert(!all_nonpositive(false)); + assert(!all_nonpositive("foo")); + assert(!all_nonpositive([0:1:2])); } -test_is_nonnegative(); +test_all_nonpositive(); + + +module test_all_nonnegative() { + assert(!all_nonnegative(-2)); + assert(all_nonnegative(0)); + assert(all_nonnegative(2)); + assert(all_nonnegative([0,0,0])); + assert(all_nonnegative([0,1,2])); + assert(all_nonnegative([3,1,2])); + assert(!all_nonnegative([3,-1,2])); + assert(!all_nonnegative([-3,-1,-2])); + assert(!all_nonnegative([[-5,-7],[-3,-1,-2]])); + assert(!all_nonnegative([[-5,-7],[-3,1,-2]])); + assert(!all_nonnegative([[5,7],[3,-1,2]])); + assert(all_nonnegative([[5,7],[3,1,2]])); + assert(!all_nonnegative([])); + assert(!all_nonnegative(true)); + assert(!all_nonnegative(false)); + assert(!all_nonnegative("foo")); + assert(!all_nonnegative([0:1:2])); +} +test_all_nonnegative(); module test_approx() { @@ -781,6 +805,12 @@ test_back_substitute(); +module test_norm_fro(){ + assert_approx(norm_fro([[2,3,4],[4,5,6]]), 10.29563014098700); + +} test_norm_fro(); + + module test_linear_solve(){ M = [[-2,-5,-1,3], [3,7,6,2], @@ -954,6 +984,38 @@ module test_real_roots(){ test_real_roots(); + +module test_quadratic_roots(){ + assert_approx(quadratic_roots([1,4,4]),[[-2,0],[-2,0]]); + assert_approx(quadratic_roots([1,4,4],real=true),[-2,-2]); + assert_approx(quadratic_roots([1,-5,6],real=true), [2,3]); + assert_approx(quadratic_roots([1,-5,6]), [[2,0],[3,0]]); +} +test_quadratic_roots(); + + +module test_null_space(){ + assert_equal(null_space([[3,2,1],[3,6,3],[3,9,-3]]),[]); + + function nullcheck(A,dim) = + let(v=null_space(A)) + len(v)==dim && all_zero(A*transpose(v),eps=1e-12); + + A = [[-1, 2, -5, 2],[-3,-1,3,-3],[5,0,5,0],[3,-4,11,-4]]; + assert(nullcheck(A,1)); + + B = [ + [ 4, 1, 8, 6, -2, 3], + [ 10, 5, 10, 10, 0, 5], + [ 8, 1, 8, 8, -6, 1], + [ -8, -8, 6, -1, -8, -1], + [ 2, 2, 0, 1, 2, 1], + [ 2, -3, 10, 6, -8, 1], + ]; + assert(nullcheck(B,3)); +} +test_null_space(); + module test_qr_factor() { // Check that R is upper triangular function is_ut(R) = @@ -962,7 +1024,15 @@ module test_qr_factor() { // Test the R is upper trianglar, Q is orthogonal and qr=M function qrok(qr,M) = - is_ut(qr[1]) && approx(qr[0]*transpose(qr[0]), ident(len(qr[0]))) && approx(qr[0]*qr[1],M); + is_ut(qr[1]) && approx(qr[0]*transpose(qr[0]), ident(len(qr[0]))) && approx(qr[0]*qr[1],M) && qr[2]==ident(len(qr[2])); + + // Test the R is upper trianglar, Q is orthogonal, R diagonal non-increasing and qrp=M + function qrokpiv(qr,M) = + is_ut(qr[1]) + && approx(qr[0]*transpose(qr[0]), ident(len(qr[0]))) + && approx(qr[0]*qr[1]*transpose(qr[2]),M) + && list_decreasing([for(i=[0:1:min(len(qr[1]),len(qr[1][0]))-1]) abs(qr[1][i][i])]); + M = [[1,2,9,4,5], [6,7,8,19,10], @@ -991,6 +1061,15 @@ module test_qr_factor() { assert(qrok(qr_factor([[7]]), [[7]])); assert(qrok(qr_factor([[1,2,3]]), [[1,2,3]])); assert(qrok(qr_factor([[1],[2],[3]]), [[1],[2],[3]])); + + + assert(qrokpiv(qr_factor(M,pivot=true),M)); + assert(qrokpiv(qr_factor(select(M,0,3),pivot=true),select(M,0,3))); + assert(qrokpiv(qr_factor(transpose(select(M,0,3)),pivot=true),transpose(select(M,0,3)))); + assert(qrokpiv(qr_factor(B,pivot=true),B)); + assert(qrokpiv(qr_factor([[7]],pivot=true), [[7]])); + assert(qrokpiv(qr_factor([[1,2,3]],pivot=true), [[1,2,3]])); + assert(qrokpiv(qr_factor([[1],[2],[3]],pivot=true), [[1],[2],[3]])); } test_qr_factor(); diff --git a/tests/test_vectors.scad b/tests/test_vectors.scad index 743112f..2e00705 100644 --- a/tests/test_vectors.scad +++ b/tests/test_vectors.scad @@ -14,6 +14,14 @@ module test_is_vector() { assert(is_vector([0,0,0],zero=false) == false); assert(is_vector([0,1,0],zero=true) == false); assert(is_vector([0,0,1],zero=false) == true); + assert(is_vector([1,1,1],zero=false) == true); + + assert(is_vector([0,0,0],all_nonzero=true) == false); + assert(is_vector([0,1,0],all_nonzero=true) == false); + assert(is_vector([0,0,1],all_nonzero=true) == false); + assert(is_vector([1,1,1],all_nonzero=true) == true); + assert(is_vector([-1,1,1],all_nonzero=true) == true); + assert(is_vector([-1,-1,-1],all_nonzero=true) == true); } test_is_vector(); diff --git a/vectors.scad b/vectors.scad index 1749981..3b83eda 100644 --- a/vectors.scad +++ b/vectors.scad @@ -19,7 +19,8 @@ // Arguments: // v = The value to test to see if it is a vector. // length = If given, make sure the vector is `length` items long. -// zero = If false, require that the length of the vector is not approximately zero. If true, require the length of the vector to be approximately zero-length. Default: `undef` (don't check vector length.) +// zero = If false, require that the length/`norm()` of the vector is not approximately zero. If true, require the length/`norm()` of the vector to be approximately zero-length. Default: `undef` (don't check vector length/`norm()`.) +// all_nonzero = If true, requires all elements of the vector to be more than `eps` different from zero. Default: `false` // eps = The minimum vector length that is considered non-zero. Default: `EPSILON` (`1e-9`) // Example: // is_vector(4); // Returns false @@ -30,14 +31,17 @@ // is_vector([3,4,5],3); // Returns true // is_vector([3,4,5],4); // Returns true // is_vector([]); // Returns false -// is_vector([0,4,0],3,zero=false); // Returns true -// is_vector([0,0,0],zero=false); // Returns false -// is_vector([0,0,1e-12],zero=false); // Returns false -// is_vector([],zero=false); // Returns false -function is_vector(v,length,zero,eps=EPSILON) = +// is_vector([0,4,0],3,zero=false); // Returns true +// is_vector([0,0,0],zero=false); // Returns false +// is_vector([0,0,1e-12],zero=false); // Returns false +// is_vector([0,1,0],all_nonzero=false); // Returns false +// is_vector([1,1,1],all_nonzero=false); // Returns true +// is_vector([],zero=false); // Returns false +function is_vector(v, length, zero, all_nonzero=false, eps=EPSILON) = is_list(v) && is_num(0*(v*v)) && (is_undef(length) || len(v)==length) - && (is_undef(zero) || ((norm(v) >= eps) == !zero)); + && (is_undef(zero) || ((norm(v) >= eps) == !zero)) + && (!all_nonzero || all_nonzero(v)) ; // Function: vang() diff --git a/version.scad b/version.scad index bdbdafa..2ca6341 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,415]; +BOSL_VERSION = [2,0,421]; // Section: BOSL Library Version Functions