| 1 | # rational_funcs.tcl -- |
|---|
| 2 | # Implement procedures to deal with rational functions |
|---|
| 3 | # |
|---|
| 4 | |
|---|
| 5 | package require math::polynomials |
|---|
| 6 | |
|---|
| 7 | namespace eval ::math::rationalfunctions { |
|---|
| 8 | variable count 0 ;# Count the number of specific commands |
|---|
| 9 | namespace eval v {} |
|---|
| 10 | |
|---|
| 11 | namespace export rationalFunction ratioCmd evalRatio \ |
|---|
| 12 | coeffsNumerator coeffsDenominator \ |
|---|
| 13 | derivRatio \ |
|---|
| 14 | addRatio subRatio multRatio \ |
|---|
| 15 | divRatio |
|---|
| 16 | |
|---|
| 17 | namespace import ::math::polynomials::* |
|---|
| 18 | } |
|---|
| 19 | |
|---|
| 20 | |
|---|
| 21 | # rationalFunction -- |
|---|
| 22 | # Return a rational function definition |
|---|
| 23 | # |
|---|
| 24 | # Arguments: |
|---|
| 25 | # num The coefficients of the numerator |
|---|
| 26 | # den The coefficients of the denominator |
|---|
| 27 | # Result: |
|---|
| 28 | # Rational function definition |
|---|
| 29 | # |
|---|
| 30 | proc ::math::rationalfunctions::rationalFunction {num den} { |
|---|
| 31 | |
|---|
| 32 | foreach coeffs [list $num $den] { |
|---|
| 33 | foreach coeff $coeffs { |
|---|
| 34 | if { ! [string is double -strict $coeff] } { |
|---|
| 35 | return -code error "Coefficients must be real numbers" |
|---|
| 36 | } |
|---|
| 37 | } |
|---|
| 38 | } |
|---|
| 39 | |
|---|
| 40 | # |
|---|
| 41 | # The leading coefficient must be non-zero |
|---|
| 42 | # |
|---|
| 43 | return [list RATIONAL_FUNCTION [polynomial $num] [polynomial $den]] |
|---|
| 44 | } |
|---|
| 45 | |
|---|
| 46 | # ratioCmd -- |
|---|
| 47 | # Return a procedure that implements a rational function evaluation |
|---|
| 48 | # |
|---|
| 49 | # Arguments: |
|---|
| 50 | # num The coefficients of the numerator |
|---|
| 51 | # den The coefficients of the denominator |
|---|
| 52 | # Result: |
|---|
| 53 | # New procedure |
|---|
| 54 | # |
|---|
| 55 | proc ::math::rationalfunctions::ratioCmd {num {den {}}} { |
|---|
| 56 | variable count |
|---|
| 57 | |
|---|
| 58 | if { [llength $den] == 0 } { |
|---|
| 59 | if { [lindex $num 0] == "RATIONAL_FUNCTION" } { |
|---|
| 60 | set den [lindex $num 2] |
|---|
| 61 | set num [lindex $num 1] |
|---|
| 62 | } |
|---|
| 63 | } |
|---|
| 64 | |
|---|
| 65 | set degree1 [expr {[llength $num]-1}] |
|---|
| 66 | set degree2 [expr {[llength $num]-1}] |
|---|
| 67 | set body "expr \{([join $num +\$x*(][string repeat ) $degree1])/\ |
|---|
| 68 | (double([join $den +\$x*(][string repeat ) $degree2])\}" |
|---|
| 69 | |
|---|
| 70 | incr count |
|---|
| 71 | set name "::math::rationalfunctions::v::RATIO$count" |
|---|
| 72 | proc $name {x} $body |
|---|
| 73 | return $name |
|---|
| 74 | } |
|---|
| 75 | |
|---|
| 76 | # evalRatio -- |
|---|
| 77 | # Evaluate a rational function at a given coordinate |
|---|
| 78 | # |
|---|
| 79 | # Arguments: |
|---|
| 80 | # ratio Rational function definition |
|---|
| 81 | # x Coordinate |
|---|
| 82 | # Result: |
|---|
| 83 | # Value at x |
|---|
| 84 | # |
|---|
| 85 | proc ::math::rationalfunctions::evalRatio {ratio x} { |
|---|
| 86 | if { [lindex $ratio 0] != "RATIONAL_FUNCTION" } { |
|---|
| 87 | return -code error "Not a rational function" |
|---|
| 88 | } |
|---|
| 89 | if { ! [string is double $x] } { |
|---|
| 90 | return -code error "Coordinate must be a real number" |
|---|
| 91 | } |
|---|
| 92 | |
|---|
| 93 | set num 0.0 |
|---|
| 94 | foreach c [lindex [lindex $ratio 1] 1] { |
|---|
| 95 | set num [expr {$num*$x+$c}] |
|---|
| 96 | } |
|---|
| 97 | |
|---|
| 98 | set den 0.0 |
|---|
| 99 | foreach c [lindex [lindex $ratio 2] 1] { |
|---|
| 100 | set den [expr {$den*$x+$c}] |
|---|
| 101 | } |
|---|
| 102 | return [expr {$num/double($den)}] |
|---|
| 103 | } |
|---|
| 104 | |
|---|
| 105 | # coeffsNumerator -- |
|---|
| 106 | # Return the coefficients of the numerator |
|---|
| 107 | # |
|---|
| 108 | # Arguments: |
|---|
| 109 | # ratio Rational function definition |
|---|
| 110 | # Result: |
|---|
| 111 | # The coefficients in ascending order |
|---|
| 112 | # |
|---|
| 113 | proc ::math::rationalfunctions::coeffsNumerator {ratio} { |
|---|
| 114 | if { [lindex $ratio 0] != "RATIONAL_FUNCTION" } { |
|---|
| 115 | return -code error "Not a rational function" |
|---|
| 116 | } |
|---|
| 117 | set polyn [lindex $ratio 1] |
|---|
| 118 | return [allCoeffsPolyn $polyn] |
|---|
| 119 | } |
|---|
| 120 | |
|---|
| 121 | # coeffsDenominator -- |
|---|
| 122 | # Return the coefficients of the denominator |
|---|
| 123 | # |
|---|
| 124 | # Arguments: |
|---|
| 125 | # ratio Rational function definition |
|---|
| 126 | # Result: |
|---|
| 127 | # The coefficients in ascending order |
|---|
| 128 | # |
|---|
| 129 | proc ::math::rationalfunctions::coeffsDenominator {ratio} { |
|---|
| 130 | if { [lindex $ratio 0] != "RATIONAL_FUNCTION" } { |
|---|
| 131 | return -code error "Not a rational function" |
|---|
| 132 | } |
|---|
| 133 | set polyn [lindex $ratio 2] |
|---|
| 134 | return [allCoeffsPolyn $polyn] |
|---|
| 135 | } |
|---|
| 136 | |
|---|
| 137 | # derivRatio -- |
|---|
| 138 | # Return the derivative of the rational function |
|---|
| 139 | # |
|---|
| 140 | # Arguments: |
|---|
| 141 | # polyn Polynomial definition |
|---|
| 142 | # Result: |
|---|
| 143 | # The new polynomial |
|---|
| 144 | # |
|---|
| 145 | proc ::math::rationalfunctions::derivRatio {ratio} { |
|---|
| 146 | if { [lindex $ratio 0] != "RATIONAL_FUNCTION" } { |
|---|
| 147 | return -code error "Not a rational function" |
|---|
| 148 | } |
|---|
| 149 | set num_polyn [lindex $ratio 1] |
|---|
| 150 | set den_polyn [lindex $ratio 2] |
|---|
| 151 | set num_deriv [derivPolyn $num_polyn] |
|---|
| 152 | set den_deriv [derivPolyn $den_polyn] |
|---|
| 153 | set num [subPolyn [multPolyn $num_deriv $den_polyn] \ |
|---|
| 154 | [multPolyn $den_deriv $num_polyn] ] |
|---|
| 155 | set den [multPolyn $den_polyn $den_polyn] |
|---|
| 156 | |
|---|
| 157 | return [list RATIONAL_FUNCTION $num $den] |
|---|
| 158 | } |
|---|
| 159 | |
|---|
| 160 | # addRatio -- |
|---|
| 161 | # Add two rational functions and return the result |
|---|
| 162 | # |
|---|
| 163 | # Arguments: |
|---|
| 164 | # ratio1 First rational function or a scalar |
|---|
| 165 | # ratio2 Second rational function or a scalar |
|---|
| 166 | # Result: |
|---|
| 167 | # The sum of the two functions |
|---|
| 168 | # Note: |
|---|
| 169 | # TODO: Check for the same denominator |
|---|
| 170 | # |
|---|
| 171 | proc ::math::rationalfunctions::addRatio {ratio1 ratio2} { |
|---|
| 172 | if { [llength $ratio1] == 1 && [string is double -strict $ratio1] } { |
|---|
| 173 | set polyn1 [rationalFunction $ratio1 1.0] |
|---|
| 174 | } |
|---|
| 175 | if { [llength $ratio2] == 1 && [string is double -strict $ratio2] } { |
|---|
| 176 | set ratio2 [rationalFunction $ratio1 1.0] |
|---|
| 177 | } |
|---|
| 178 | if { [lindex $ratio1 0] != "RATIONAL_FUNCTION" || |
|---|
| 179 | [lindex $ratio2 0] != "RATIONAL_FUNCTION" } { |
|---|
| 180 | return -code error "Both arguments must be rational functions or a real number" |
|---|
| 181 | } |
|---|
| 182 | |
|---|
| 183 | set num1 [lindex $ratio1 1] |
|---|
| 184 | set den1 [lindex $ratio1 2] |
|---|
| 185 | set num2 [lindex $ratio2 1] |
|---|
| 186 | set den2 [lindex $ratio2 2] |
|---|
| 187 | |
|---|
| 188 | set newnum [addPolyn [multPolyn $num1 $den2] \ |
|---|
| 189 | [multPolyn $num2 $den1] ] |
|---|
| 190 | |
|---|
| 191 | set newden [multPolyn $den1 $den2] |
|---|
| 192 | |
|---|
| 193 | return [list RATIONAL_FUNCTION $newnum $newden] |
|---|
| 194 | } |
|---|
| 195 | |
|---|
| 196 | # subRatio -- |
|---|
| 197 | # Subtract two rational functions and return the result |
|---|
| 198 | # |
|---|
| 199 | # Arguments: |
|---|
| 200 | # ratio1 First rational function or a scalar |
|---|
| 201 | # ratio2 Second rational function or a scalar |
|---|
| 202 | # Result: |
|---|
| 203 | # The difference of the two functions |
|---|
| 204 | # Note: |
|---|
| 205 | # TODO: Check for the same denominator |
|---|
| 206 | # |
|---|
| 207 | proc ::math::rationalfunctions::subRatio {ratio1 ratio2} { |
|---|
| 208 | if { [llength $ratio1] == 1 && [string is double -strict $ratio1] } { |
|---|
| 209 | set polyn1 [rationalFunction $ratio1 1.0] |
|---|
| 210 | } |
|---|
| 211 | if { [llength $ratio2] == 1 && [string is double -strict $ratio2] } { |
|---|
| 212 | set ratio2 [rationalFunction $ratio1 1.0] |
|---|
| 213 | } |
|---|
| 214 | if { [lindex $ratio1 0] != "RATIONAL_FUNCTION" || |
|---|
| 215 | [lindex $ratio2 0] != "RATIONAL_FUNCTION" } { |
|---|
| 216 | return -code error "Both arguments must be rational functions or a real number" |
|---|
| 217 | } |
|---|
| 218 | |
|---|
| 219 | set num1 [lindex $ratio1 1] |
|---|
| 220 | set den1 [lindex $ratio1 2] |
|---|
| 221 | set num2 [lindex $ratio2 1] |
|---|
| 222 | set den2 [lindex $ratio2 2] |
|---|
| 223 | |
|---|
| 224 | set newnum [subPolyn [multPolyn $num1 $den2] \ |
|---|
| 225 | [multPolyn $num2 $den1] ] |
|---|
| 226 | |
|---|
| 227 | set newden [multPolyn $den1 $den2] |
|---|
| 228 | |
|---|
| 229 | return [list RATIONAL_FUNCTION $newnum $newden] |
|---|
| 230 | } |
|---|
| 231 | |
|---|
| 232 | # multRatio -- |
|---|
| 233 | # Multiply two rational functions and return the result |
|---|
| 234 | # |
|---|
| 235 | # Arguments: |
|---|
| 236 | # ratio1 First rational function or a scalar |
|---|
| 237 | # ratio2 Second rational function or a scalar |
|---|
| 238 | # Result: |
|---|
| 239 | # The product of the two functions |
|---|
| 240 | # Note: |
|---|
| 241 | # TODO: Check for the same denominator |
|---|
| 242 | # |
|---|
| 243 | proc ::math::rationalfunctions::multRatio {ratio1 ratio2} { |
|---|
| 244 | if { [llength $ratio1] == 1 && [string is double -strict $ratio1] } { |
|---|
| 245 | set polyn1 [rationalFunction $ratio1 1.0] |
|---|
| 246 | } |
|---|
| 247 | if { [llength $ratio2] == 1 && [string is double -strict $ratio2] } { |
|---|
| 248 | set ratio2 [rationalFunction $ratio1 1.0] |
|---|
| 249 | } |
|---|
| 250 | if { [lindex $ratio1 0] != "RATIONAL_FUNCTION" || |
|---|
| 251 | [lindex $ratio2 0] != "RATIONAL_FUNCTION" } { |
|---|
| 252 | return -code error "Both arguments must be rational functions or a real number" |
|---|
| 253 | } |
|---|
| 254 | |
|---|
| 255 | set num1 [lindex $ratio1 1] |
|---|
| 256 | set den1 [lindex $ratio1 2] |
|---|
| 257 | set num2 [lindex $ratio2 1] |
|---|
| 258 | set den2 [lindex $ratio2 2] |
|---|
| 259 | |
|---|
| 260 | set newnum [multPolyn $num1 $num2] |
|---|
| 261 | set newden [multPolyn $den1 $den2] |
|---|
| 262 | |
|---|
| 263 | return [list RATIONAL_FUNCTION $newnum $newden] |
|---|
| 264 | } |
|---|
| 265 | |
|---|
| 266 | # divRatio -- |
|---|
| 267 | # Divide two rational functions and return the result |
|---|
| 268 | # |
|---|
| 269 | # Arguments: |
|---|
| 270 | # ratio1 First rational function or a scalar |
|---|
| 271 | # ratio2 Second rational function or a scalar |
|---|
| 272 | # Result: |
|---|
| 273 | # The quotient of the two functions |
|---|
| 274 | # Note: |
|---|
| 275 | # TODO: Check for the same denominator |
|---|
| 276 | # |
|---|
| 277 | proc ::math::rationalfunctions::divRatio {ratio1 ratio2} { |
|---|
| 278 | if { [llength $ratio1] == 1 && [string is double -strict $ratio1] } { |
|---|
| 279 | set polyn1 [rationalFunction $ratio1 1.0] |
|---|
| 280 | } |
|---|
| 281 | if { [llength $ratio2] == 1 && [string is double -strict $ratio2] } { |
|---|
| 282 | set ratio2 [rationalFunction $ratio1 1.0] |
|---|
| 283 | } |
|---|
| 284 | if { [lindex $ratio1 0] != "RATIONAL_FUNCTION" || |
|---|
| 285 | [lindex $ratio2 0] != "RATIONAL_FUNCTION" } { |
|---|
| 286 | return -code error "Both arguments must be rational functions or a real number" |
|---|
| 287 | } |
|---|
| 288 | |
|---|
| 289 | set num1 [lindex $ratio1 1] |
|---|
| 290 | set den1 [lindex $ratio1 2] |
|---|
| 291 | set num2 [lindex $ratio2 1] |
|---|
| 292 | set den2 [lindex $ratio2 2] |
|---|
| 293 | |
|---|
| 294 | set newnum [multPolyn $num1 $den2] |
|---|
| 295 | set newden [multPolyn $num2 $den1] |
|---|
| 296 | |
|---|
| 297 | return [list RATIONAL_FUNCTION $newnum $newden] |
|---|
| 298 | } |
|---|
| 299 | |
|---|
| 300 | # |
|---|
| 301 | # Announce our presence |
|---|
| 302 | # |
|---|
| 303 | package provide math::rationalfunctions 1.0.1 |
|---|
| 304 | |
|---|
| 305 | # some tests -- |
|---|
| 306 | # |
|---|
| 307 | if { 0 } { |
|---|
| 308 | set tcl_precision 17 |
|---|
| 309 | |
|---|
| 310 | set f1 [::math::rationalfunctions::rationalFunction {1 2 3} {1 4}] |
|---|
| 311 | set f2 [::math::rationalfunctions::rationalFunction {1 2 3 0} {1 4}] |
|---|
| 312 | set f3 [::math::rationalfunctions::rationalFunction {0 0 0 0} {1}] |
|---|
| 313 | set f4 [::math::rationalfunctions::rationalFunction {5 7} {1}] |
|---|
| 314 | set cmdf1 [::math::rationalfunctions::ratioCmd {1 2 3} {1 4}] |
|---|
| 315 | |
|---|
| 316 | foreach x {0 1 2 3 4 5} { |
|---|
| 317 | puts "[::math::rationalfunctions::evalRatio $f1 $x] -- \ |
|---|
| 318 | [expr {(1.0+2.0*$x+3.0*$x*$x)/double(1.0+4.0*$x)}] -- \ |
|---|
| 319 | [$cmdf1 $x] -- [::math::rationalfunctions::evalRatio $f3 $x]" |
|---|
| 320 | } |
|---|
| 321 | |
|---|
| 322 | puts "All coefficients = [::math::rationalfunctions::coeffsNumerator $f2]" |
|---|
| 323 | puts " [::math::rationalfunctions::coeffsDenominator $f2]" |
|---|
| 324 | |
|---|
| 325 | puts "Derivative = [::math::rationalfunctions::derivRatio $f1]" |
|---|
| 326 | |
|---|
| 327 | puts "Add: [::math::rationalfunctions::addRatio $f1 $f4]" |
|---|
| 328 | puts "Add: [::math::rationalfunctions::addRatio $f4 $f1]" |
|---|
| 329 | puts "Subtract: [::math::rationalfunctions::subRatio $f1 $f4]" |
|---|
| 330 | puts "Multiply: [::math::rationalfunctions::multRatio $f1 $f4]" |
|---|
| 331 | |
|---|
| 332 | set f1 [::math::rationalfunctions::rationalFunction {1 2 3} 1] |
|---|
| 333 | set f2 [::math::rationalfunctions::rationalFunction {0 1} 1] |
|---|
| 334 | |
|---|
| 335 | puts "Divide: [::math::rationalfunctions::divRatio $f1 $f2]" |
|---|
| 336 | |
|---|
| 337 | set f1 [::math::rationalfunctions::rationalFunction {1 2 3} 1] |
|---|
| 338 | set f2 [::math::rationalfunctions::rationalFunction {1 1} {1 2}] |
|---|
| 339 | |
|---|
| 340 | puts "Divide: [::math::rationalfunctions::divRatio $f1 $f2]" |
|---|
| 341 | |
|---|
| 342 | set f1 [::math::rationalfunctions::rationalFunction {1 2 3} 1] |
|---|
| 343 | set f2 [::math::rationalfunctions::rationalFunction {0 1} {0 0 1}] |
|---|
| 344 | set f3 [::math::rationalfunctions::divRatio $f2 $f1] |
|---|
| 345 | set coeffs [::math::rationalfunctions::coeffsNumerator $f3] |
|---|
| 346 | puts "Coefficients: $coeffs" |
|---|
| 347 | set f3 [::math::rationalfunctions::divRatio $f1 $f2] |
|---|
| 348 | set coeffs [::math::rationalfunctions::coeffsNumerator $f3] |
|---|
| 349 | puts "Coefficients: $coeffs" |
|---|
| 350 | set f1 [::math::rationalfunctions::rationalFunction {1 2 3} {1 2}] |
|---|
| 351 | set f2 [::math::rationalfunctions::rationalFunction {0} {1}] |
|---|
| 352 | set f3 [::math::rationalfunctions::divRatio $f2 $f1] |
|---|
| 353 | set coeffs [::math::rationalfunctions::coeffsNumerator $f3] |
|---|
| 354 | puts "Coefficients: $coeffs" |
|---|
| 355 | puts "Eval null function: [::math::rationalfunctions::evalRatio $f2 1]" |
|---|
| 356 | } |
|---|