Merge branch 'master' into develop
authorSteven Baltakatei Sandoval <baltakatei@gmail.com>
Thu, 13 Jan 2022 05:01:06 +0000 (05:01 +0000)
committerSteven Baltakatei Sandoval <baltakatei@gmail.com>
Thu, 13 Jan 2022 05:01:06 +0000 (05:01 +0000)
unitproc/bk_export_audio.sh [new file with mode: 0755]
unitproc/bktemp-checkAppFileDir
unitproc/bktemp-checkFlt [new file with mode: 0644]
unitproc/bktemp-initGitRepo
unitproc/bktemp-updateLoopPI [new file with mode: 0644]
unitproc/octave/invgaurnd.m [new file with mode: 0644]
unitproc/python/sleepRand.py

diff --git a/unitproc/bk_export_audio.sh b/unitproc/bk_export_audio.sh
new file mode 100755 (executable)
index 0000000..7befc0b
--- /dev/null
@@ -0,0 +1,286 @@
+#!/bin/bash
+# Desc: Extracts audio from video files
+# Usage: bk_export_audio.sh [input_dir] ([output_dir])
+# Version: 0.0.2
+
+declare -Ag appRollCall # Associative array for storing app status
+declare -Ag fileRollCall # Associative array for storing file status
+declare -Ag dirRollCall # Associative array for storing dir status
+
+yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
+die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
+try() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
+checkapp() {
+    # Desc: If arg is a command, save result in assoc array 'appRollCall'
+    # Usage: checkapp arg1 arg2 arg3 ...
+    # Version: 0.1.1
+    # Input: global assoc. array 'appRollCall'
+    # Output: adds/updates key(value) to global assoc array 'appRollCall'
+    # Depends: bash 5.0.3
+    local returnState    
+
+    #===Process Args===
+    for arg in "$@"; do
+       if command -v "$arg" 1>/dev/null 2>&1; then # Check if arg is a valid command
+           appRollCall[$arg]="true";
+           if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
+       else
+           appRollCall[$arg]="false"; returnState="false";
+       fi;
+    done;
+
+    #===Determine function return code===
+    if [ "$returnState" = "true" ]; then
+       return 0;
+    else
+       return 1;
+    fi;
+} # Check that app exists
+checkfile() {
+    # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
+    # Usage: checkfile arg1 arg2 arg3 ...
+    # Version: 0.1.1
+    # Input: global assoc. array 'fileRollCall'
+    # Output: adds/updates key(value) to global assoc array 'fileRollCall';
+    # Output: returns 0 if app found, 1 otherwise
+    # Depends: bash 5.0.3
+    local returnState
+
+    #===Process Args===
+    for arg in "$@"; do
+       if [ -f "$arg" ]; then
+           fileRollCall["$arg"]="true";
+           if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
+       else
+           fileRollCall["$arg"]="false"; returnState="false";
+       fi;
+    done;
+    
+    #===Determine function return code===
+    if [ "$returnState" = "true" ]; then
+       return 0;
+    else
+       return 1;
+    fi;
+} # Check that file exists
+checkdir() {
+    # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
+    # Usage: checkdir arg1 arg2 arg3 ...
+    # Version 0.1.2
+    # Input: global assoc. array 'dirRollCall'
+    # Output: adds/updates key(value) to global assoc array 'dirRollCall';
+    # Output: returns 0 if all args are dirs; 1 otherwise
+    # Depends: Bash 5.0.3
+    local returnState
+
+    #===Process Args===
+    for arg in "$@"; do
+       if [ -z "$arg" ]; then
+           dirRollCall["(Unspecified Dirname(s))"]="false"; returnState="false";
+       elif [ -d "$arg" ]; then
+           dirRollCall["$arg"]="true";
+           if ! [ "$returnState" = "false" ]; then returnState="true"; fi
+       else
+           dirRollCall["$arg"]="false"; returnState="false";
+       fi
+    done
+    
+    #===Determine function return code===
+    if [ "$returnState" = "true" ]; then
+       return 0;
+    else
+       return 1;
+    fi
+} # Check that dir exists
+displayMissing() {
+    # Desc: Displays missing apps, files, and dirs
+    # Usage: displayMissing
+    # Version 0.1.1
+    # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
+    # Output: stderr: messages indicating missing apps, file, or dirs
+    # Depends: bash 5, checkAppFileDir()
+    local missingApps value appMissing missingFiles fileMissing
+    local missingDirs dirMissing
+    
+    #==BEGIN Display errors==
+    #===BEGIN Display Missing Apps===
+    missingApps="Missing apps  :";
+    #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
+    for key in "${!appRollCall[@]}"; do
+       value="${appRollCall[$key]}";
+       if [ "$value" = "false" ]; then
+           #echo "DEBUG:Missing apps: $key => $value";
+           missingApps="$missingApps""$key ";
+           appMissing="true";
+       fi;
+    done;
+    if [ "$appMissing" = "true" ]; then  # Only indicate if an app is missing.
+       echo "$missingApps" 1>&2;
+    fi;
+    unset value;
+    #===END Display Missing Apps===
+
+    #===BEGIN Display Missing Files===
+    missingFiles="Missing files:";
+    #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
+    for key in "${!fileRollCall[@]}"; do
+       value="${fileRollCall[$key]}";
+       if [ "$value" = "false" ]; then
+           #echo "DEBUG:Missing files: $key => $value";
+           missingFiles="$missingFiles""$key ";
+           fileMissing="true";
+       fi;
+    done;
+    if [ "$fileMissing" = "true" ]; then  # Only indicate if an app is missing.
+       echo "$missingFiles" 1>&2;
+    fi;
+    unset value;
+    #===END Display Missing Files===
+
+    #===BEGIN Display Missing Directories===
+    missingDirs="Missing dirs:";
+    #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
+    for key in "${!dirRollCall[@]}"; do
+       value="${dirRollCall[$key]}";
+       if [ "$value" = "false" ]; then
+           #echo "DEBUG:Missing dirs: $key => $value";
+           missingDirs="$missingDirs""$key ";
+           dirMissing="true";
+       fi;
+    done;
+    if [ "$dirMissing" = "true" ]; then  # Only indicate if an dir is missing.
+       echo "$missingDirs" 1>&2;
+    fi;
+    unset value;
+    #===END Display Missing Directories===
+
+    #==END Display errors==
+} # Display missing apps, files, dirs
+showUsage() {
+    # Desc: Display script usage information
+    # Usage: showUsage
+    # Version 0.0.1
+    # Input: none
+    # Output: stdout
+    # Depends: GNU-coreutils 8.30 (cat)
+    cat <<'EOF'
+    USAGE:
+        bk_export_audio.sh [DIR in] ([DIR out])
+
+    EXAMPLE:
+      bk_export_audio.sh ./videos/ ./exported_audio/
+      bk_export_audio.sh ./videos/
+EOF
+} # Display information on how to use this script.
+get_audio_format() {
+    # Desc: Gets audio format of file as string
+    # Usage: get_audio_format arg1
+    # Depends: ffprobe
+    # Version: 0.0.1
+    # Input: arg1: input file path
+    # Output: stdout (if valid audio format)
+    #         exit code 0 if audio file; 1 otherwise
+    # Example: get_audio_format myvideo.mp4
+    #   Note: Would return "opus" if full ffprobe report had 'Audio: opus, 48000 Hz, stereo, fltp'
+    # Note: Not tested with videos containing multiple video streams
+    # Ref/Attrib: [1] https://stackoverflow.com/questions/5618363/is-there-a-way-to-use-ffmpeg-to-determine-the-encoding-of-a-file-before-transcod
+    #             [2] https://stackoverflow.com/questions/44123532/how-to-find-out-the-file-extension-for-extracting-audio-tracks-with-ffmpeg-and-p#comment88464070_50723126
+    local audio_format file_in;
+    local return_state;
+    file_in="$1";
+
+    # Return error exit code if not audio file
+    ## Return error if ffprobe itself exited on error
+    if ! ffprobe -v error -select_streams a -show_entries stream=codec_name -of default=nokey=1:noprint_wrappers=1 "$file_in" 1>/dev/null 2>&1; then
+       return_state="false";
+    fi;
+    
+    # Get audio format
+    audio_format="$(ffprobe -v error -select_streams a -show_entries stream=codec_name -of default=nokey=1:noprint_wrappers=1 "$file_in")"; # see [1]
+
+    ## Return error if audio format is incorrectly formatted (e.g. reject if contains spaces)
+    pattern="^[[:alnum:]]+$"; # alphanumeric string with no spaces
+    if [[ $audio_format =~ $pattern ]]; then
+       return_state="true";
+       # Report audio format
+       echo "$audio_format";
+    else
+       return_state="false";
+    fi;
+    
+    # Report exit code
+    if [[ $return_state = "true" ]]; then
+       return 0;
+    else
+       return 1;
+    fi;
+} # Get audio format as stdout
+extract_audio_file() {
+    # Desc: Use ffmpeg to creates audio file from input video file
+    # Usage: extract_audio_file arg1 arg2 arg3
+    # Depends: ffmpeg
+    # Input: arg1: input video file path
+    #        arg2: desired output file extension
+    #        arg3: output dir path
+    # Output: audio file at path [arg3]/[arg1].[arg2]
+    local file_in file_in_ext dir_out file_in_basename;
+    file_in="$1";
+    file_in_ext="$2";
+    dir_out="$3";
+
+    # Extract audio file
+    file_in_basename="$(basename "$file_in")";    
+    ffmpeg -i "$file_in" -vn -acodec copy "$dir_out"/"$file_in_basename"."$file_in_ext";
+} # Create audio file from video file
+
+main() {
+    script_pwd="$(pwd)";
+    dir_in="$1";
+    dir_out="$2";
+
+    # Check argument count
+    if [[ $# -lt 1 ]]; then
+       showUsage;
+       die "ERROR:Not enough arguments:$#";
+    fi;
+
+    # Check apps, dirs
+    checkapp ffmpeg ffprobe date;
+    displayMissing;
+
+    if ! checkdir "$dir_in"; then
+       showUsage;
+       displayMissing;
+       die "ERROR:Missing input directory."
+    fi;
+    if ! checkdir "$dir_out"; then
+       yell "NOTICE:Output directory not specified. Creating output directory in current working directory:$script_pwd";
+       timestamp="$(date +%Y%m%dT%H%M%S%z)"; # iso-8601 without separators
+       dir_out="$script_pwd"/"$timestamp"..output;
+       try mkdir "$dir_out";
+    fi;
+
+    # Do work
+    yell "DEBUG:dir_in:$dir_in":
+    yell "DEBUG:dir_out:$dir_out";
+    for file in "$dir_in"/*; do
+       aud_format="$(get_audio_format "$file")"; # Get audio format as file extension string
+       file_basename="$(basename "$file")"; # Get basename for debugging
+       yell "DEBUG:file_basename:$file_basename";
+       yell "DEBUG:aud_format:$aud_format";
+       yell "DEBUG:";
+
+       # Ignore files that return blank $aud_format
+       if [[ -z $aud_format ]]; then
+           yell "DEBUG:Not an audio file:$file";
+           continue;
+       fi;
+       
+       extract_audio_file "$file" "$aud_format" "$dir_out";
+    done;
+} # main program
+
+main "$@";
+
+# Author: Steven Baltaktei Sandoval
+# License: GPLv3+
index d5a620bbf09e422397002c2e1801b9fa8305f114..6059be64df7bbaf3dad0a2839c5727e3cbfe2fd6 100644 (file)
@@ -65,16 +65,18 @@ checkfile() {
 checkdir() {
     # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
     # Usage: checkdir arg1 arg2 arg3 ...
-    # Version 0.1.1
+    # Version 0.1.2
     # Input: global assoc. array 'dirRollCall'
     # Output: adds/updates key(value) to global assoc array 'dirRollCall';
-    # Output: returns 0 if app found, 1 otherwise
+    # Output: returns 0 if all args are dirs; 1 otherwise
     # Depends: Bash 5.0.3
     local returnState
 
     #===Process Args===
     for arg in "$@"; do
-       if [ -d "$arg" ]; then
+       if [ -z "$arg" ]; then
+           dirRollCall["(Unspecified Dirname(s))"]="false"; returnState="false";
+       elif [ -d "$arg" ]; then
            dirRollCall["$arg"]="true";
            if ! [ "$returnState" = "false" ]; then returnState="true"; fi
        else
diff --git a/unitproc/bktemp-checkFlt b/unitproc/bktemp-checkFlt
new file mode 100644 (file)
index 0000000..0a0d6e4
--- /dev/null
@@ -0,0 +1,83 @@
+#!/bin/bash
+# Desc: Checks if arg is a float
+
+#==BEGIN Define script parameters==
+#===BEGIN Declare local script functions===
+yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
+die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
+try() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
+checkFlt() {
+    # Desc: Checks if arg is a float
+    # Usage: checkFlt arg
+    # Input: arg: float
+    # Output: - return code 0 (if arg is float)
+    #         - return code 1 (if arg is not float)
+    # Example: if ! checkFlt $arg; then echo "not flt"; fi;
+    # Version: 0.0.2
+    # Depends: yell(), die(), bash 5.0.3
+    # Ref/Attrib: JDB https://stackoverflow.com/a/12643073 float regex
+    local returnState
+
+    #===Process Arg===
+    if [[ $# -ne 1 ]]; then
+       die "ERROR:Invalid number of arguments:$#";
+    fi;
+
+    RETEST1='^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$'; # Regular Expression to test
+    if [[ ! $1 =~ $RETEST1 ]] ; then
+       returnState="false";
+    else
+       returnState="true";
+    fi;
+
+    #===Determine function return code===
+    if [ "$returnState" = "true" ]; then
+       return 0;
+    else
+       return 1;
+    fi;
+} # Checks if arg is float
+
+#===END Declare local script functions===
+#==END Define script parameters==
+
+#==BEGIN test code==
+myVar1="4" ; echo "Test 1:Should succeed because int is float without decimal places or decimal.";
+(if checkFlt "$myVar1"; then yell "success"; else yell "fail"; fi;) & sleep 1;
+
+myVar1="4.0" ; echo "Test 2:Should succeed because \"4.0\" is a float.";
+(if checkFlt "$myVar1"; then yell "success"; else yell "fail"; fi;) & sleep 1;
+
+myVar1=".0" ; echo "Test 3:Should succeed even if float lack whole numbers left of decimal.";
+(if checkFlt "$myVar1"; then yell "success"; else yell "fail"; fi;) & sleep 1;
+
+myVar1="4." ; echo "Test 4:Should succeed even if float lacks decimal places right of decimal.";
+(if checkFlt "$myVar1"; then yell "success"; else yell "fail"; fi;) & sleep 1;
+
+myVar1="14.0" ; echo "Test 5:Should succeed with multiple whole numbers to left of decimal."
+(if checkFlt "$myVar1"; then yell "success"; else yell "fail"; fi;) & sleep 1;
+
+myVar1="." ; echo "Test 6:Should fail because neither whole numbers nor decimal places are present.";
+(if checkFlt "$myVar1"; then yell "success"; else yell "fail"; fi;) & sleep 1;
+
+myVar1="4"; myVar2="5"; echo "Test 7:Should fail because multiple numbers are provided.";
+(if checkFlt "$myVar1" "$myVar2"; then yell "success"; else yell "fail"; fi;) & sleep 1;
+
+myVar1="4 5"; echo "Test 8:Should fail because multiple numbers are provided.";
+(if checkFlt "$myVar1"; then yell "success"; else yell "fail"; fi;) & sleep 1;
+
+myVar1="4.4.4"; echo "Test 9:Should fail because a float should contain only one decimal.";
+(if checkFlt "$myVar1"; then yell "success"; else yell "fail"; fi;) & sleep 1;
+
+myVar1="foo"; echo "Test 10:Should fail because floats should only contain numbers and decimal characters."
+(if checkFlt "$myVar1"; then yell "success"; else yell "fail"; fi;) & sleep 1;
+
+myVar1="foo"; myVar2="bar"; myVar3="baz"; echo "Test 11: Should fail because multiple arguments provided.";
+(if checkFlt "$myVar1" "$myVar2" "$myVar3"; then yell "success"; else yell "fail"; fi;) & sleep 1;
+
+myVar1=""; echo "Test 12: Should fil because empty string.";
+(if checkFlt "$myVar1"; then yell "success"; else yell "fail"; fi;) & sleep 1;
+#==END test code==
+
+# Author: Steven Baltakatei Sandoval
+# License: GPLv3+
index 3e4b5663c65314c2d72ba9cf588a9f4d91874c65..911a63c3bde38c621cb2ed7a47ee6f992cc891e9 100644 (file)
@@ -31,7 +31,7 @@ initGitRepo() {
     #        arg2: repoDir
     #        arg3: remoteName
     #        arg4: branchName
-    # Version: 0.0.7
+    # Version: 0.0.8
     # Depends: checkURL() 0.0.2, yell(), Bash 5.0.3
     # Ref/Attrib: [1]: Test for space-less alphanuemric string. https://unix.stackexchange.com/a/416120
     #             [2]: Test for argument count. https://stackoverflow.com/q/18568706
@@ -77,6 +77,7 @@ initGitRepo() {
     git remote add "$remoteName" "$repoURL";
     yell "STATUS:Pulling branch $branchName from remote $remoteName";
     git pull --ff-only "$remoteName" "$branchName";
+    git fetch "$remoteName";
     unset repoURL repoDir remoteName branchName;
     popd || exit 1;
     #==END create and populate git repository==
diff --git a/unitproc/bktemp-updateLoopPI b/unitproc/bktemp-updateLoopPI
new file mode 100644 (file)
index 0000000..9054b90
--- /dev/null
@@ -0,0 +1,160 @@
+#!/bin/bash
+
+#==BEGIN function definition==
+yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
+die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
+try() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
+update_pi() {
+    # Desc: Calculates control variable (CV) given setpoint (SP) and
+    #   process variable (PV). Uses proportional integral (PI)
+    #   control.
+    # Usage: update_pi arg1 arg2 arg3 arg4 arg5 arg6
+    # Version: 0.1.3
+    # Input: arg1: path_loop_name (control loop name path)
+    #        arg2: var_pv (process variable)
+    #        arg3: var_sp (set point)
+    #        arg4: tune_p (proportional tuning factor)
+    #        arg5: tune_i (integral tuning factor)
+    #        arg6: var_cv_bias (control variable bias; prevents initial jerk)
+    # Output: stdout: var_cv (control variable)
+    #         file: path_var_pv
+    #         file: path_var_sp
+    #         file: path_tune_p
+    #         file: path_tune_i
+    #         file: path_var_cv
+    #         file: path_sum (saves updated sum state)
+    # Example: update_pi /dev/shm/DC1.AC 1.0 3.0 2.0 3.0 
+    # Depends: bc, gnu coreutils 8.30, yell(), try()
+    local var_sp var_pv var_cv;
+    local tune_p tune_i;
+    local term_p term_i;
+    local error sum sum_cand;
+    local path_sum path_var_sp path_var_pv path_tune_p path_tune_i;
+
+    path_loop_name="$1";
+
+    path_var_pv="$path_loop_name".pv
+    var_pv="$2"; # read provided process variable
+    
+    path_var_sp="$path_loop_name".sp
+    if [[ -f "$path_var_sp" ]]; then
+       var_sp="$(cat "$path_var_sp" | head -n1)";
+    else
+       var_sp="$3";
+    fi;
+    
+    path_tune_p="$path_loop_name".tune_p
+    if [[ -f "$path_tune_p" ]]; then
+       tune_p="$(cat "$path_tune_p" | head -n1)";
+    else
+       tune_p="$4";
+    fi;
+
+    path_tune_i="$path_loop_name".tune_i
+    if [[ -f "$path_tune_i" ]]; then
+       tune_i="$(cat "$path_tune_i" | head -n1)";
+    else
+       tune_i="$5";
+    fi;
+
+    path_var_cv="$path_loop_name".cv
+
+    path_var_cv_bias="$path_loop_name".cv_bias
+    var_cv_bias="$6";
+    
+    path_sum="$path_loop_name".sum
+    if [[ -f "$path_sum" ]]; then
+       sum="$(cat "$path_sum" | head -n1)";
+    else
+       sum=0;
+    fi;
+
+    #yell "DEBUG:path_loop_name:$path_loop_name";
+    #yell "DEBUG:var_pv:$var_pv";
+    #yell "DEBUG:var_sp:$var_sp";
+    #yell "DEBUG:var_cv:$var_cv";
+    #yell "DEBUG:var_cv_bias:$var_cv_bias";
+    #yell "DEBUG:tune_p:$tune_p";
+    #yell "DEBUG:tune_i:$tune_i";
+    
+    error="$(try echo "$var_sp - $var_pv" | bc -l)";
+    #yell "DEBUG:error:$error";
+    sum_cand="$(try echo "$sum + $error" | bc -l)";
+    #yell "DEBUG:sum:$sum";
+    if [[ "$(try echo "$sum_cand > 2 * $sum " | bc -l)" -eq 1 ]]; then
+       sum="$(try echo "$sum + l($error + 1)" | bc -l)"; # dampen integral sum spikes
+    else
+       sum="$sum_cand";
+    fi;
+    term_p="$(try echo "$tune_p * $error" | bc -l)";
+    #yell "DEBUG:term_p:$term_p";
+    term_i="$(try echo "$tune_i * $sum" | bc -l)";
+    #yell "DEBUG:term_i:$term_i";
+    var_cv="$(try echo "$term_p + $term_i + $var_cv_bias" | bc -l)";
+    #yell "DEBUG:var_cv:$var_cv";
+    
+    # Write variables to memory
+    echo "$sum" > "$path_sum";
+    echo "$var_sp" > "$path_var_sp";
+    echo "$var_pv" > "$path_var_pv";
+    echo "$tune_p" > "$path_tune_p";
+    echo "$tune_i" > "$path_tune_i";
+    echo "$var_cv" > "$path_var_cv";
+    echo "$var_cv_bias" > "$path_var_cv_bias";
+
+    # Output control variable to stdout
+    echo "$var_cv";
+
+    #yell "DEBUG:=============END_ROUND===============";
+} # update specified PI loop
+#==END function definition==
+
+#==BEGIN Example code==
+path_loop_name="/tmp/DC1.AC";
+var_pv=1.0;
+var_sp=2.0;
+tune_p=1.0;
+tune_i=0.1;
+var_cv_init=1000;
+try rm "$path_loop_name"*
+
+var_pv=4.0;
+output="$(update_pi "$path_loop_name" "$var_pv" "$var_sp" "$tune_p" "$tune_i" "$var_cv_init" | tail -n1)";
+yell "DEBUG:sum:$(cat "$path_loop_name".sum | head -n1)";
+yell "DEBUG:output:$output";
+var_pv=4.0;
+output="$(update_pi "$path_loop_name" "$var_pv" "$var_sp" "$tune_p" "$tune_i" "$var_cv_init" | tail -n1)";
+yell "DEBUG:sum:$(cat "$path_loop_name".sum | head -n1)";
+yell "DEBUG:output:$output";
+var_pv=4.0;
+output="$(update_pi "$path_loop_name" "$var_pv" "$var_sp" "$tune_p" "$tune_i" "$var_cv_init" | tail -n1)";
+yell "DEBUG:sum:$(cat "$path_loop_name".sum | head -n1)";
+yell "DEBUG:output:$output";
+var_pv=4.0;
+output="$(update_pi "$path_loop_name" "$var_pv" "$var_sp" "$tune_p" "$tune_i" "$var_cv_init" | tail -n1)";
+yell "DEBUG:sum:$(cat "$path_loop_name".sum | head -n1)";
+yell "DEBUG:output:$output";
+var_pv=4.0;
+output="$(update_pi "$path_loop_name" "$var_pv" "$var_sp" "$tune_p" "$tune_i" "$var_cv_init" | tail -n1)";
+yell "DEBUG:sum:$(cat "$path_loop_name".sum | head -n1)";
+yell "DEBUG:output:$output";
+var_pv=1.0;
+output="$(update_pi "$path_loop_name" "$var_pv" "$var_sp" "$tune_p" "$tune_i" "$var_cv_init" | tail -n1)";
+yell "DEBUG:sum:$(cat "$path_loop_name".sum | head -n1)";
+yell "DEBUG:output:$output";
+var_pv=1.0;
+output="$(update_pi "$path_loop_name" "$var_pv" "$var_sp" "$tune_p" "$tune_i" "$var_cv_init" | tail -n1)";
+yell "DEBUG:sum:$(cat "$path_loop_name".sum | head -n1)";
+yell "DEBUG:output:$output";
+var_pv=1.0;
+output="$(update_pi "$path_loop_name" "$var_pv" "$var_sp" "$tune_p" "$tune_i" "$var_cv_init" | tail -n1)";
+yell "DEBUG:sum:$(cat "$path_loop_name".sum | head -n1)";
+yell "DEBUG:output:$output";
+var_pv=1.0;
+output="$(update_pi "$path_loop_name" "$var_pv" "$var_sp" "$tune_p" "$tune_i" "$var_cv_init" | tail -n1)";
+yell "DEBUG:sum:$(cat "$path_loop_name".sum | head -n1)";
+yell "DEBUG:output:$output";
+#==END Example code==
+
+# Author: Steven Baltakatei Sandoval
+# License: GPLv3+
diff --git a/unitproc/octave/invgaurnd.m b/unitproc/octave/invgaurnd.m
new file mode 100644 (file)
index 0000000..02d5628
--- /dev/null
@@ -0,0 +1,151 @@
+## Copyright (C) 2012 Rik Wehbring
+## Copyright (C) 1995-2016 Kurt Hornik
+## Copyright (C) 2021 Steven Baltakatei Sandoval
+##
+## This program is free software: you can redistribute it and/or
+## modify it under the terms of the GNU General Public License as
+## published by the Free Software Foundation, either version 3 of the
+## License, or (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; see the file COPYING.  If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {} {} invgaurnd (@var{mu}, @var{lambda})
+## @deftypefnx {} {} invgaurnd (@var{mu}, @var{lambda}, @var{r})
+## @deftypefnx {} {} invgaurnd (@var{mu}, @var{lambda}, @var{r}, @var{c}, @dots{})
+## @deftypefnx {} {} invgaurnd (@var{mu}, @var{lambda}, [@var{sz}])
+## Return a matrix of random samples from the inverse gaussian distribution with
+## parameters mean @var{mu} and shape parameter @var{lambda}.
+##
+## When called with a single size argument, return a square matrix with
+## the dimension specified.  When called with more than one scalar argument the
+## first two arguments are taken as the number of rows and columns and any
+## further arguments specify additional matrix dimensions.  The size may also
+## be specified with a vector of dimensions @var{sz}.
+##
+## If no size arguments are given then the result matrix is the common size of
+## @var{mu} and @var{lambda}.
+## @end deftypefn
+
+## Author: Steven Sandoval <baltakatei@gmail.com>
+## Description: Random variates from the inverse gaussian distribution
+
+function rnd = invgaurnd (mu, lambda, varargin)
+
+  if (nargin < 2)
+    print_usage ();
+  endif
+
+  if (! isscalar (mu) || ! isscalar (lambda))
+    [retval, mu, lambda] = common_size (mu, lambda);
+    if (retval > 0)
+      error ("invgaurnd: MU and LAMBDA must be of common size or scalars");
+    endif
+  endif
+
+  if (iscomplex (mu) || iscomplex (lambda))
+    error ("invgaurnd: MU and LAMBDA must not be complex");
+  endif
+
+  if (nargin == 2)
+    sz = size (mu);
+  elseif (nargin == 3)
+    if (isscalar (varargin{1}) && varargin{1} >= 0)
+      sz = [varargin{1}, varargin{1}];
+    elseif (isrow (varargin{1}) && all (varargin{1} >= 0))
+      sz = varargin{1};
+    else
+      error ("invgaurnd: dimension vector must be row vector of non-negative integers");
+    endif
+  elseif (nargin > 3)
+    if (any (cellfun (@(x) (! isscalar (x) || x < 0), varargin)))
+      error ("invgaurnd: dimensions must be non-negative integers");
+    endif
+    sz = [varargin{:}];
+  endif
+
+  if (! isscalar (mu) && ! isequal (size (mu), sz))
+    error ("invgaurnd: MU and LAMBDA must be scalar or of size SZ");
+  endif
+
+  if (isa (mu, "single") || isa (lambda, "single"))
+    cls = "single";
+  else
+    cls = "double";
+  endif;
+
+  # Convert mu and lambda from scalars into matrices since scalar multiplication used
+  if isscalar (mu)
+    mu = mu * ones(sz);
+  endif;
+  if isscalar (lambda)
+    lambda = lambda * ones(sz);
+  endif;
+
+  # Generate random variates
+  # Ref/Attrib: Michael, John R., Generating Random Variates Using
+  #   Transformations with Multiple Roots. The American Statistician, May 1976,
+  #   Vol. 30, No. 2. https://doi.org/10.2307/2683801
+  nu = randn(sz,cls);
+  y = nu .** 2;
+  x1 = mu;
+  x2 = mu .** 2 .* y ./ (2 .* lambda);
+  x3 = (- mu ./ (2 .* lambda)) .* sqrt(4 .* mu .* lambda .* y + mu .** 2 .* y .** 2);
+  x = x1 + x2 + x3;
+  z = rand(sz,cls);
+  valTest1 = (mu ./ (mu + x)); # calculate test 1 value
+  valTest2 = (mu ./ (mu + x)); # calculate test 2 value
+  posTest1 = find(z <= valTest1); # perform test 1, save positions where test 1 true
+  posTest2 = find(z > valTest2); # perform test 2, save positions where test 2 true
+  ## indposTest1 = transpose(posTest1) # debug: list positions
+  ## indposTest2 = transpose(posTest2) # debug: list positions
+  ## indTest1 = z <= valTest1 # debug: show test 1 truth table
+  ## indTest2 = z > valTest2 # debug: show test 2 truth table
+  rnd = NaN(sz); # Initialize return array
+  rnd(posTest1) = x(posTest1); # populate return matrix with corresp. elements of x that satisfy test 1
+  rnd(posTest2) = (mu(posTest2) .** 2 ./ x(posTest2)); # populate return matrix with corresp. elements of x that satisfy test 2
+  k = ! isfinite (mu) | !(lambda >= 0) | !(lambda < Inf); # store position matrix indicating which parts of output are invalid based on
+                                                          #   elements of the matrices: mu, lambda.
+  rnd(k) = NaN; # mark invalid positions of output matrix with NaN
+
+endfunction
+
+
+%!assert (size (invgaurnd (1,2)), [1, 1])
+%!assert (size (invgaurnd (ones (2,1), 2)), [2, 1])
+%!assert (size (invgaurnd (ones (2,2), 2)), [2, 2])
+%!assert (size (invgaurnd (1, 2*ones (2,1))), [2, 1])
+%!assert (size (invgaurnd (1, 2*ones (2,2))), [2, 2])
+%!assert (size (invgaurnd (1, 2, 3)), [3, 3])
+%!assert (size (invgaurnd (1, 2, [4 1])), [4, 1])
+%!assert (size (invgaurnd (1, 2, 4, 1)), [4, 1])
+
+## Test class of input preserved
+%!assert (class (invgaurnd (1, 2)), "double")
+%!assert (class (invgaurnd (single (1), 2)), "single")
+%!assert (class (invgaurnd (single ([1 1]), 2)), "single")
+%!assert (class (invgaurnd (1, single (2))), "single")
+%!assert (class (invgaurnd (1, single ([2 2]))), "single")
+
+## Test input validation
+%!error invgaurnd ()
+%!error invgaurnd (1)
+%!error invgaurnd (ones (3), ones (2))
+%!error invgaurnd (ones (2), ones (3))
+%!error invgaurnd (i, 2)
+%!error invgaurnd (2, i)
+%!error invgaurnd (1,2, -1)
+%!error invgaurnd (1,2, ones (2))
+%!error invgaurnd (1, 2, [2 -1 2])
+%!error invgaurnd (1,2, 1, ones (2))
+%!error invgaurnd (1,2, 1, -1)
+%!error invgaurnd (ones (2,2), 2, 3)
+%!error invgaurnd (ones (2,2), 2, [3, 2])
+%!error invgaurnd (ones (2,2), 2, 2, 3)
index 7a9b2b74e2674d115a34225b6860db637dcfd61b..016e9f396c4a77aeed3a1de9bca251e6c1582853 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 # Desc: Pauses a random amount of time. Random distribution is inverse gaussian.
-# Version: 0.0.5
+# Version: 0.0.6
 # Depends: python 3.7.3
 # Usage: ./sleepRand.py [-v] [-p P] SECONDS
 # Input: SECONDS: float seconds (mean of inverse gaussian distribution)
@@ -34,6 +34,13 @@ parser.add_argument('--precision','-p',
                     default=[4.0],
                     type=float,
                     help='How concentrated delays are around the mean (default: 4.0). Must be a positive integer or floating point value. Is the lambda factor in the inverse gaussian distribution. High values (e.g. > 10.0) cause random delays to rarely stray far from MEAN. Small values (e.g. < 0.10) result in many small delays plus occasional long delays.');
+parser.add_argument('--upper','-u',
+                    action='store',
+                    metavar='U',
+                    nargs=1,
+                    default=[None],
+                    type=float,
+                    help='Upper bound for possible delays (default: no bound). Without bound, extremely high delays are unlikely but possible.');
 args = parser.parse_args();
 
 # Define functions
@@ -73,14 +80,28 @@ logging.debug('DEBUG:Debug logging output enabled.');
 logging.debug('DEBUG:args.verbosity:' + str(args.verbosity));
 logging.debug('DEBUG:args:' + str(args));
 
-## Reject negative floats.
+## Receive input arguments
 try:
     ### Get desired mean
     desMean = args.mean[0];    
     logging.debug('DEBUG:Desired mean:' + str(desMean));
+    
     ### Get lambda precision factor
     lambdaFactor = args.precision[0];
     logging.debug('DEBUG:Lambda precision factor:' + str(lambdaFactor));
+    
+    ### Get upper bound
+    if isinstance(args.upper[0], float):
+        logging.debug('DEBUG:args.upper[0] is float:' + str(args.upper[0]));
+        upperBound = args.upper[0];
+    elif args.upper[0] is None:
+        logging.debug('DEBUG:args.upper[0] is None:' + str(args.upper[0]));
+        upperBound = None;
+    else:
+        raise TypeError('Upper bound not set correctly.');
+    logging.debug('DEBUG:Upper bound:' + str(upperBound));
+    
+    ### Reject negative floats.
     if desMean < 0:
         logging.error('ERROR:Desired mean is negative:' + str(desMean));
         raise ValueError('Negative number error.');
@@ -91,8 +112,13 @@ except ValueError:
     sys.exit(1);
 
 # Calculate delay
-delay = randInvGau(desMean, desMean * lambdaFactor);
-logging.debug('delay:' + str(delay));
+rawDelay = randInvGau(desMean, desMean * lambdaFactor);
+logging.debug('DEBUG:rawDelay(seconds):' + str(rawDelay));
+if isinstance(upperBound,float):
+    delay = min(upperBound, rawDelay);
+elif upperBound is None:
+    delay = rawDelay;
+logging.debug('DEBUG:delay(seconds)   :' + str(delay));
 
 # Sleep
 time.sleep(float(delay));