From 7c42c7f5e37114343dc34be3c4d93861f88ba581 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Mon, 31 Aug 2020 17:31:55 -0700 Subject: [PATCH 01/12] Added libfile names to index. --- scripts/genindex.sh | 36 ++++++++++++++++++++++++++++-------- version.scad | 2 +- 2 files changed, 29 insertions(+), 9 deletions(-) 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/version.scad b/version.scad index bdbdafa..1d40e98 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,415]; +BOSL_VERSION = [2,0,416]; // Section: BOSL Library Version Functions From 38a4c12032b43f37936345ffe0a7b3657302f6d5 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Mon, 31 Aug 2020 18:03:48 -0700 Subject: [PATCH 02/12] Cheat Sheet generation script bugfixes. --- scripts/gencheat.sh | 17 ++++++++++++----- version.scad | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/scripts/gencheat.sh b/scripts/gencheat.sh index 6762f0a..da801c2 100755 --- a/scripts/gencheat.sh +++ b/scripts/gencheat.sh @@ -26,7 +26,7 @@ function columnize hdrln1="| $(ucase $1) " hdrln2='|:-----' n=1 - while [[ $n < $maxcols ]] ; do + while [[ $n -lt $maxcols ]] ; do hdrln1+=' |  ' hdrln2+=' |:------' n=$(($n+1)) @@ -34,7 +34,7 @@ function columnize hdrln1+=' |' hdrln2+=' |' n=0 - while [[ $n < $maxrows ]] ; do + while [[ $n -lt $maxrows ]] ; do lines[$n]="" n=$(($n+1)) done @@ -56,7 +56,7 @@ function columnize echo $hdrln1 echo $hdrln2 n=0 - while [[ $n < $maxrows ]] ; do + while [[ $n -lt $maxrows ]] ; do echo "| ${lines[$n]} |" n=$(($n+1)) done @@ -67,6 +67,11 @@ 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}' @@ -80,9 +85,11 @@ CHEAT_FILES=$(grep '^include' std.scad | sed 's/^.*<\([a-zA-Z0-9.]*\)>/\1/'|grep echo '( [Alphabetic Index](Index) )' echo for f in $CHEAT_FILES ; do - #echo "### $f" ( - egrep -H 'Constant: ' $f | mkconstindex + ( + grep -H 'Constant: ' $f | mkconstindex + grep -H '^[A-Z$][A-Z0-9_]* *=.*//' $f | mkconstindex2 + ) | sort -u egrep -H 'Function: |Function&Module: |Module: ' $f | mkotherindex ) | columnize $f echo diff --git a/version.scad b/version.scad index 1d40e98..ffa8d27 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,416]; +BOSL_VERSION = [2,0,417]; // Section: BOSL Library Version Functions From cb36b5d94f539d41b31e206fba2b88f76a8527f3 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 1 Sep 2020 00:54:27 -0700 Subject: [PATCH 03/12] Bugfixes for cheat sheet generation. --- .github/workflows/docsgen.yml | 5 ++ scripts/gencheat.sh | 98 +++++++++++++++++------------------ version.scad | 2 +- 3 files changed, 55 insertions(+), 50 deletions(-) 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/scripts/gencheat.sh b/scripts/gencheat.sh index da801c2..6fa49ec 100755 --- a/scripts/gencheat.sh +++ b/scripts/gencheat.sh @@ -16,50 +16,52 @@ function columnize cols=4 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 -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]+=" | " + 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 -lt $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 @@ -84,14 +86,12 @@ CHEAT_FILES=$(grep '^include' std.scad | sed 's/^.*<\([a-zA-Z0-9.]*\)>/\1/'|grep 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' for f in $CHEAT_FILES ; do - ( - ( - grep -H 'Constant: ' $f | mkconstindex - grep -H '^[A-Z$][A-Z0-9_]* *=.*//' $f | mkconstindex2 - ) | sort -u - egrep -H 'Function: |Function&Module: |Module: ' $f | mkotherindex - ) | columnize $f + egrep -H '// Function: |// Function&Module: |// Module: ' $f | mkotherindex | sort -u | columnize $f echo done ) > BOSL2.wiki/CheatSheet.md diff --git a/version.scad b/version.scad index ffa8d27..6ef424a 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,417]; +BOSL_VERSION = [2,0,418]; // Section: BOSL Library Version Functions From 8331d8e8032b209d5b60b8c3a40322faeaab5de9 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 1 Sep 2020 01:23:01 -0700 Subject: [PATCH 04/12] Cheat Sheet ordering tweak. --- scripts/gencheat.sh | 8 ++++---- std.scad | 28 +++++++++++++--------------- version.scad | 2 +- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/scripts/gencheat.sh b/scripts/gencheat.sh index 6fa49ec..e07a909 100755 --- a/scripts/gencheat.sh +++ b/scripts/gencheat.sh @@ -13,7 +13,7 @@ function lcase function columnize { - cols=4 + cols=$2 TMPFILE=$(mktemp -t $(basename $0).XXXXXX) || exit 1 cat >>$TMPFILE if [[ $(wc -l $TMPFILE | awk '{print $1}') -gt 1 ]] ; then @@ -79,7 +79,7 @@ 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' @@ -89,9 +89,9 @@ CHEAT_FILES=$(grep '^include' std.scad | sed 's/^.*<\([a-zA-Z0-9.]*\)>/\1/'|grep ( grep -H '// Constant: ' $CHEAT_FILES | mkconstindex grep -H '^[A-Z$][A-Z0-9_]* *=.*//' $CHEAT_FILES | mkconstindex2 - ) | sort -u | columnize 'Constants' + ) | sort -u | columnize 'Constants' 6 for f in $CHEAT_FILES ; do - egrep -H '// Function: |// Function&Module: |// Module: ' $f | mkotherindex | sort -u | columnize $f + egrep -H '// Function: |// Function&Module: |// Module: ' $f | mkotherindex | columnize "[$f]($f)" 4 echo done ) > BOSL2.wiki/CheatSheet.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/version.scad b/version.scad index 6ef424a..262b891 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,418]; +BOSL_VERSION = [2,0,419]; // Section: BOSL Library Version Functions From 8f6c2e8538db046fc6c061a3ff774a507193570e Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Tue, 1 Sep 2020 16:42:47 -0400 Subject: [PATCH 05/12] Add submatrix_set and block_matrix and tests --- arrays.scad | 36 ++++++++++++++++++++++++++++++++++++ tests/test_arrays.scad | 25 +++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/arrays.scad b/arrays.scad index e2d3ac2..4d22be2 100644 --- a/arrays.scad +++ b/arrays.scad @@ -1199,6 +1199,42 @@ 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: 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)) + 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 && j Date: Tue, 1 Sep 2020 17:57:31 -0400 Subject: [PATCH 06/12] Added norm_fro, quadratic_roots and pivoting to qr_factor and linear_solve. Added tests. --- math.scad | 97 ++++++++++++++++++++++++++++++++---------- tests/test_arrays.scad | 1 + tests/test_math.scad | 34 ++++++++++++++- 3 files changed, 108 insertions(+), 24 deletions(-) diff --git a/math.scad b/math.scad index 509db44..a66e1cf 100644 --- a/math.scad +++ b/math.scad @@ -680,7 +680,7 @@ function convolve(p,q) = // 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) = +function linear_solve(A,b,pivot=true) = assert(is_matrix(A), "Input should be a matrix.") let( m = len(A), @@ -688,19 +688,17 @@ function linear_solve(A,b) = ) assert(is_vector(b,m) || is_matrix(b,m),"Invalid right hand side or incompatible with the matrix") let ( - qr = mj ? 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,6 +869,17 @@ 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) = + sqrt(sum([for(entry=A) sum_of_squares(entry)])); + + // Section: Comparisons and Logic // Function: is_zero() @@ -1309,6 +1327,39 @@ 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. + +// 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/tests/test_arrays.scad b/tests/test_arrays.scad index 90ec305..eaec5b3 100644 --- a/tests/test_arrays.scad +++ b/tests/test_arrays.scad @@ -492,6 +492,7 @@ module test_submatrix_set() { 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(); diff --git a/tests/test_math.scad b/tests/test_math.scad index c3eb601..7b53cc6 100644 --- a/tests/test_math.scad +++ b/tests/test_math.scad @@ -781,6 +781,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 +960,15 @@ 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_qr_factor() { // Check that R is upper triangular function is_ut(R) = @@ -962,7 +977,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 +1014,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(); From 399c40f7a6fd9b0d48c02e2ac9e26d629beeb7c5 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Tue, 1 Sep 2020 18:38:31 -0400 Subject: [PATCH 07/12] Added null_space and diagonal_matrix --- arrays.scad | 10 ++++++++++ math.scad | 17 +++++++++++++++++ tests/test_arrays.scad | 8 ++++++++ tests/test_math.scad | 23 +++++++++++++++++++++++ 4 files changed, 58 insertions(+) diff --git a/arrays.scad b/arrays.scad index 4d22be2..8f44fe8 100644 --- a/arrays.scad +++ b/arrays.scad @@ -1216,6 +1216,16 @@ function block_matrix(M) = 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) = + [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]) diff --git a/math.scad b/math.scad index a66e1cf..6e4be5c 100644 --- a/math.scad +++ b/math.scad @@ -712,6 +712,23 @@ function matrix_inverse(A) = assert(is_matrix(A,square=true),"Input to matrix_inverse() must be a square matrix") linear_solve(A,ident(len(A))); +// Function: null_space() +// Usage: +// null_space(A) +// Description: +// Returns an orthonormal basis for the null space of A, namely the vectors {x} such that Ax=0. If the null space +// is just the origin then returns an empty list. +function null_space(A,eps=1e-12) = + assert(is_matrix(A)) + let( + Q_R=qr_factor(transpose(A),pivot=true), + R=Q_R[1], + zrow = [for(i=idx(R)) if (is_zero(R[i],eps)) i] + ) + len(zrow)==0 + ? [] + : transpose(subindex(Q_R[0],zrow)); + // Function: qr_factor() // Usage: qr = qr_factor(A,[pivot]) diff --git a/tests/test_arrays.scad b/tests/test_arrays.scad index eaec5b3..1dfe421 100644 --- a/tests/test_arrays.scad +++ b/tests/test_arrays.scad @@ -481,6 +481,14 @@ module test_block_matrix() { 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]]; diff --git a/tests/test_math.scad b/tests/test_math.scad index 7b53cc6..9a25cec 100644 --- a/tests/test_math.scad +++ b/tests/test_math.scad @@ -969,6 +969,29 @@ module test_quadratic_roots(){ } 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 && is_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) = From 47a1dfaa237aea221038530ea41df955b5677d0e Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Tue, 1 Sep 2020 18:43:53 -0400 Subject: [PATCH 08/12] doc tweak --- math.scad | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/math.scad b/math.scad index 6e4be5c..599a0c4 100644 --- a/math.scad +++ b/math.scad @@ -1350,7 +1350,9 @@ function C_div(z1,z2) = // 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. +// 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 From 3dd8524db726cbf55f96471649322d54dc98dc10 Mon Sep 17 00:00:00 2001 From: Kelvie Wong Date: Tue, 1 Sep 2020 20:42:01 -0700 Subject: [PATCH 09/12] Fix offset calculations in dovetail. We need to properly account for slop in back_width, otherwise the front offset can end up being positive at small scales. --- joiners.scad | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/joiners.scad b/joiners.scad index 1178d4e..1d76c36 100644 --- a/joiners.scad +++ b/joiners.scad @@ -517,8 +517,10 @@ module dovetail(gender, length, l, width, w, height, h, angle, slope, taper, bac assert(count3<=1 || (radius==0 && chamfer==0), "Do not specify both chamfer and radius"); slope = is_def(slope) ? slope : is_def(angle) ? 1/tan(angle) : 6; - width = gender == "male" ? w : w + 2*$slop; - height = h + (gender == "female" ? 2*$slop : 0); + extra_slop = gender == "female" ? 2*$slop : 0; + width = w + extra_slop; + height = h + extra_slop; + back_width = back_width + extra_slop; front_offset = is_def(taper) ? -extra * tan(taper) : is_def(back_width) ? extra * (back_width-width)/length/2 : 0; From 9cd91cc6cca371b2b91236cf9dbe718a8afd2a37 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 2 Sep 2020 16:46:58 -0400 Subject: [PATCH 10/12] Faster version of norm_fro, and some arg validation --- arrays.scad | 3 +++ math.scad | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/arrays.scad b/arrays.scad index 8f44fe8..37fee58 100644 --- a/arrays.scad +++ b/arrays.scad @@ -1224,6 +1224,7 @@ function block_matrix(M) = // 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]]; @@ -1237,6 +1238,8 @@ function diagonal_matrix(diag,offdiag=0) = 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]) diff --git a/math.scad b/math.scad index 599a0c4..934d332 100644 --- a/math.scad +++ b/math.scad @@ -894,7 +894,8 @@ function is_matrix(A,m,n,square=false) = // 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) = - sqrt(sum([for(entry=A) sum_of_squares(entry)])); + assert(is_matrix(A) || is_vector(A)) + norm(flatten(A)); // Section: Comparisons and Logic From a9b8f5618a421d6ad48e3a7a7f610dc9b5ba42c4 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Sun, 6 Sep 2020 17:15:08 -0700 Subject: [PATCH 11/12] Fix for Issue #263 --- math.scad | 115 +++++++++++++++-------------- tests/test_math.scad | 172 +++++++++++++++++++++---------------------- version.scad | 2 +- 3 files changed, 145 insertions(+), 144 deletions(-) diff --git a/math.scad b/math.scad index 934d332..10b7463 100644 --- a/math.scad +++ b/math.scad @@ -712,6 +712,7 @@ function matrix_inverse(A) = assert(is_matrix(A,square=true),"Input to matrix_inverse() must be a square matrix") linear_solve(A,ident(len(A))); + // Function: null_space() // Usage: // null_space(A) @@ -723,7 +724,7 @@ function null_space(A,eps=1e-12) = let( Q_R=qr_factor(transpose(A),pivot=true), R=Q_R[1], - zrow = [for(i=idx(R)) if (is_zero(R[i],eps)) i] + zrow = [for(i=idx(R)) if (all_zero(R[i],eps)) i] ) len(zrow)==0 ? [] @@ -900,9 +901,9 @@ function norm_fro(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`. // If passed a list, recursively checks if all items in the list are approximately zero. @@ -911,19 +912,19 @@ function norm_fro(A) = // 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] == []) : +// 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_list(x)? (x != [] && [for (xx=x) if(!all_zero(xx,eps=eps)) 1] == []) : is_num(x)? approx(x,eps) : false; -// Function: is_positive() +// Function: all_positive() // Usage: -// is_positive(x); +// all_positive(x); // Description: // Returns true if the number passed to it is greater than zero. // If passed a list, recursively checks if all items in the list are positive. @@ -931,22 +932,22 @@ function is_zero(x, eps=EPSILON) = // 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_list(x)? (x != [] && [for (xx=x) if(!all_positive(xx)) 1] == []) : is_num(x)? x>0 : 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. // If passed a list, recursively checks if all items in the list are negative. @@ -954,23 +955,23 @@ function is_positive(x) = // 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_list(x)? (x != [] && [for (xx=x) if(!all_negative(xx)) 1] == []) : is_num(x)? x<0 : 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. // If passed a list, recursively checks if all items in the list are nonpositive. @@ -978,23 +979,23 @@ function is_negative(x) = // 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_list(x)? (x != [] && [for (xx=x) if(!all_nonpositive(xx)) 1] == []) : is_num(x)? x<=0 : 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. // If passed a list, recursively checks if all items in the list are nonnegative. @@ -1002,17 +1003,17 @@ function is_nonpositive(x) = // 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_list(x)? (x != [] && [for (xx=x) if(!all_nonnegative(xx)) 1] == []) : is_num(x)? x>=0 : false; diff --git a/tests/test_math.scad b/tests/test_math.scad index 9a25cec..720235c 100644 --- a/tests/test_math.scad +++ b/tests/test_math.scad @@ -100,104 +100,104 @@ 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_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_positive(); +test_all_positive(); -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_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_negative(); +test_all_negative(); -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_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_nonpositive(); +test_all_nonpositive(); -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_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_is_nonnegative(); +test_all_nonnegative(); module test_approx() { @@ -975,7 +975,7 @@ module test_null_space(){ function nullcheck(A,dim) = let(v=null_space(A)) - len(v)==dim && is_zero(A*transpose(v),eps=1e-12); + 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)); diff --git a/version.scad b/version.scad index 262b891..86a9e2e 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,419]; +BOSL_VERSION = [2,0,420]; // Section: BOSL Library Version Functions From b2121fbfb361363ec658523c95fbf24e6c2b9ba2 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Mon, 7 Sep 2020 23:10:39 -0700 Subject: [PATCH 12/12] Added all_nonzero(). Added is_vector(all_nonzero=). --- math.scad | 42 +++++++++++++++++++++++++++++++---------- tests/test_math.scad | 24 +++++++++++++++++++++++ tests/test_vectors.scad | 8 ++++++++ vectors.scad | 18 +++++++++++------- version.scad | 2 +- 5 files changed, 76 insertions(+), 18 deletions(-) diff --git a/math.scad b/math.scad index 10b7463..935c44a 100644 --- a/math.scad +++ b/math.scad @@ -905,7 +905,7 @@ function norm_fro(A) = // Usage: // 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: @@ -917,8 +917,30 @@ function norm_fro(A) = // 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] == []) : - is_num(x)? approx(x,eps) : + false; + + +// Function: all_nonzero() +// Usage: +// all_nonzero(x); +// Description: +// 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; @@ -926,7 +948,7 @@ function all_zero(x, eps=EPSILON) = // Usage: // all_positive(x); // Description: -// Returns true if the number passed to it is greater than zero. +// 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: @@ -940,8 +962,8 @@ function all_zero(x, eps=EPSILON) = // all_positive([3,1,2]); // Returns: true. // all_positive([3,-1,2]); // Returns: false. function all_positive(x) = - is_list(x)? (x != [] && [for (xx=x) if(!all_positive(xx)) 1] == []) : is_num(x)? x>0 : + is_list(x)? (x != [] && [for (xx=x) if(!all_positive(xx)) 1] == []) : false; @@ -949,7 +971,7 @@ function all_positive(x) = // Usage: // 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: @@ -964,8 +986,8 @@ function all_positive(x) = // all_negative([3,-1,2]); // Returns: false. // all_negative([-3,-1,-2]); // Returns: true. function all_negative(x) = - is_list(x)? (x != [] && [for (xx=x) if(!all_negative(xx)) 1] == []) : is_num(x)? x<0 : + is_list(x)? (x != [] && [for (xx=x) if(!all_negative(xx)) 1] == []) : false; @@ -973,7 +995,7 @@ function all_negative(x) = // Usage: // 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: @@ -988,8 +1010,8 @@ function all_negative(x) = // all_nonpositive([3,-1,2]); // Returns: false. // all_nonpositive([-3,-1,-2]); // Returns: true. function all_nonpositive(x) = - is_list(x)? (x != [] && [for (xx=x) if(!all_nonpositive(xx)) 1] == []) : is_num(x)? x<=0 : + is_list(x)? (x != [] && [for (xx=x) if(!all_nonpositive(xx)) 1] == []) : false; @@ -997,7 +1019,7 @@ function all_nonpositive(x) = // Usage: // 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: @@ -1013,8 +1035,8 @@ function all_nonpositive(x) = // all_nonnegative([3,-1,2]); // Returns: false. // all_nonnegative([-3,-1,-2]); // Returns: false. function all_nonnegative(x) = - is_list(x)? (x != [] && [for (xx=x) if(!all_nonnegative(xx)) 1] == []) : is_num(x)? x>=0 : + is_list(x)? (x != [] && [for (xx=x) if(!all_nonnegative(xx)) 1] == []) : false; diff --git a/tests/test_math.scad b/tests/test_math.scad index 720235c..5641416 100644 --- a/tests/test_math.scad +++ b/tests/test_math.scad @@ -122,6 +122,30 @@ module test_all_zero() { test_all_zero(); +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_all_nonzero(); + + module test_all_positive() { assert(!all_positive(-2)); assert(!all_positive(0)); 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 86a9e2e..2ca6341 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,420]; +BOSL_VERSION = [2,0,421]; // Section: BOSL Library Version Functions