George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 1 | #!/usr/bin/env julia |
| 2 | # -*- julia -*- |
| 3 | |
| 4 | # remez.jl - implementation of the Remez algorithm for polynomial approximation |
| 5 | # |
Szabolcs Nagy | 11253b0 | 2018-11-12 11:10:57 +0000 | [diff] [blame] | 6 | # Copyright (c) 2015-2018, Arm Limited. |
| 7 | # SPDX-License-Identifier: MIT |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 8 | |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 9 | import Base.\ |
| 10 | |
| 11 | # ---------------------------------------------------------------------- |
| 12 | # Helper functions to cope with different Julia versions. |
| 13 | if VERSION >= v"0.7.0" |
| 14 | array1d(T, d) = Array{T, 1}(undef, d) |
| 15 | array2d(T, d1, d2) = Array{T, 2}(undef, d1, d2) |
| 16 | else |
| 17 | array1d(T, d) = Array(T, d) |
| 18 | array2d(T, d1, d2) = Array(T, d1, d2) |
| 19 | end |
| 20 | if VERSION < v"0.5.0" |
| 21 | String = ASCIIString |
| 22 | end |
| 23 | if VERSION >= v"0.6.0" |
| 24 | # Use Base.invokelatest to run functions made using eval(), to |
| 25 | # avoid "world age" error |
| 26 | run(f, x...) = Base.invokelatest(f, x...) |
| 27 | else |
| 28 | # Prior to 0.6.0, invokelatest doesn't exist (but fortunately the |
| 29 | # world age problem also doesn't seem to exist) |
| 30 | run(f, x...) = f(x...) |
| 31 | end |
| 32 | |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 33 | # ---------------------------------------------------------------------- |
| 34 | # Global variables configured by command-line options. |
| 35 | floatsuffix = "" # adjusted by --floatsuffix |
| 36 | xvarname = "x" # adjusted by --variable |
| 37 | epsbits = 256 # adjusted by --bits |
| 38 | debug_facilities = Set() # adjusted by --debug |
| 39 | full_output = false # adjusted by --full |
| 40 | array_format = false # adjusted by --array |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 41 | preliminary_commands = array1d(String, 0) # adjusted by --pre |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 42 | |
| 43 | # ---------------------------------------------------------------------- |
| 44 | # Diagnostic and utility functions. |
| 45 | |
| 46 | # Enable debugging printouts from a particular subpart of this |
| 47 | # program. |
| 48 | # |
| 49 | # Arguments: |
| 50 | # facility Name of the facility to debug. For a list of facility names, |
| 51 | # look through the code for calls to debug(). |
| 52 | # |
| 53 | # Return value is a BigFloat. |
| 54 | function enable_debug(facility) |
| 55 | push!(debug_facilities, facility) |
| 56 | end |
| 57 | |
| 58 | # Print a diagnostic. |
| 59 | # |
| 60 | # Arguments: |
| 61 | # facility Name of the facility for which this is a debug message. |
| 62 | # printargs Arguments to println() if debugging of that facility is |
| 63 | # enabled. |
| 64 | macro debug(facility, printargs...) |
| 65 | printit = quote |
| 66 | print("[", $facility, "] ") |
| 67 | end |
| 68 | for arg in printargs |
| 69 | printit = quote |
| 70 | $printit |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 71 | print($(esc(arg))) |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 72 | end |
| 73 | end |
| 74 | return quote |
| 75 | if $facility in debug_facilities |
| 76 | $printit |
| 77 | println() |
| 78 | end |
| 79 | end |
| 80 | end |
| 81 | |
| 82 | # Evaluate a polynomial. |
| 83 | |
| 84 | # Arguments: |
| 85 | # coeffs Array of BigFloats giving the coefficients of the polynomial. |
| 86 | # Starts with the constant term, i.e. coeffs[i] is the |
| 87 | # coefficient of x^(i-1) (because Julia arrays are 1-based). |
| 88 | # x Point at which to evaluate the polynomial. |
| 89 | # |
| 90 | # Return value is a BigFloat. |
| 91 | function poly_eval(coeffs::Array{BigFloat}, x::BigFloat) |
| 92 | n = length(coeffs) |
| 93 | if n == 0 |
| 94 | return BigFloat(0) |
| 95 | elseif n == 1 |
| 96 | return coeffs[1] |
| 97 | else |
| 98 | return coeffs[1] + x * poly_eval(coeffs[2:n], x) |
| 99 | end |
| 100 | end |
| 101 | |
| 102 | # Evaluate a rational function. |
| 103 | |
| 104 | # Arguments: |
| 105 | # ncoeffs Array of BigFloats giving the coefficients of the numerator. |
| 106 | # Starts with the constant term, and 1-based, as above. |
| 107 | # dcoeffs Array of BigFloats giving the coefficients of the denominator. |
| 108 | # Starts with the constant term, and 1-based, as above. |
| 109 | # x Point at which to evaluate the function. |
| 110 | # |
| 111 | # Return value is a BigFloat. |
| 112 | function ratfn_eval(ncoeffs::Array{BigFloat}, dcoeffs::Array{BigFloat}, |
| 113 | x::BigFloat) |
| 114 | return poly_eval(ncoeffs, x) / poly_eval(dcoeffs, x) |
| 115 | end |
| 116 | |
| 117 | # Format a BigFloat into an appropriate output format. |
| 118 | # Arguments: |
| 119 | # x BigFloat to format. |
| 120 | # |
| 121 | # Return value is a string. |
| 122 | function float_to_str(x) |
| 123 | return string(x) * floatsuffix |
| 124 | end |
| 125 | |
| 126 | # Format a polynomial into an arithmetic expression, for pasting into |
| 127 | # other tools such as gnuplot. |
| 128 | |
| 129 | # Arguments: |
| 130 | # coeffs Array of BigFloats giving the coefficients of the polynomial. |
| 131 | # Starts with the constant term, and 1-based, as above. |
| 132 | # |
| 133 | # Return value is a string. |
| 134 | function poly_to_string(coeffs::Array{BigFloat}) |
| 135 | n = length(coeffs) |
| 136 | if n == 0 |
| 137 | return "0" |
| 138 | elseif n == 1 |
| 139 | return float_to_str(coeffs[1]) |
| 140 | else |
| 141 | return string(float_to_str(coeffs[1]), "+", xvarname, "*(", |
| 142 | poly_to_string(coeffs[2:n]), ")") |
| 143 | end |
| 144 | end |
| 145 | |
| 146 | # Format a rational function into a string. |
| 147 | |
| 148 | # Arguments: |
| 149 | # ncoeffs Array of BigFloats giving the coefficients of the numerator. |
| 150 | # Starts with the constant term, and 1-based, as above. |
| 151 | # dcoeffs Array of BigFloats giving the coefficients of the denominator. |
| 152 | # Starts with the constant term, and 1-based, as above. |
| 153 | # |
| 154 | # Return value is a string. |
| 155 | function ratfn_to_string(ncoeffs::Array{BigFloat}, dcoeffs::Array{BigFloat}) |
| 156 | if length(dcoeffs) == 1 && dcoeffs[1] == 1 |
| 157 | # Special case: if the denominator is just 1, leave it out. |
| 158 | return poly_to_string(ncoeffs) |
| 159 | else |
| 160 | return string("(", poly_to_string(ncoeffs), ")/(", |
| 161 | poly_to_string(dcoeffs), ")") |
| 162 | end |
| 163 | end |
| 164 | |
| 165 | # Format a list of x,y pairs into a string. |
| 166 | |
| 167 | # Arguments: |
| 168 | # xys Array of (x,y) pairs of BigFloats. |
| 169 | # |
| 170 | # Return value is a string. |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 171 | function format_xylist(xys::Array{Tuple{BigFloat,BigFloat}}) |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 172 | return ("[\n" * |
| 173 | join([" "*string(x)*" -> "*string(y) for (x,y) in xys], "\n") * |
| 174 | "\n]") |
| 175 | end |
| 176 | |
| 177 | # ---------------------------------------------------------------------- |
| 178 | # Matrix-equation solver for matrices of BigFloat. |
| 179 | # |
| 180 | # I had hoped that Julia's type-genericity would allow me to solve the |
| 181 | # matrix equation Mx=V by just writing 'M \ V'. Unfortunately, that |
| 182 | # works by translating the inputs into double precision and handing |
| 183 | # off to an optimised library, which misses the point when I have a |
| 184 | # matrix and vector of BigFloat and want my result in _better_ than |
| 185 | # double precision. So I have to implement my own specialisation of |
| 186 | # the \ operator for that case. |
| 187 | # |
| 188 | # Fortunately, the point of using BigFloats is that we have precision |
| 189 | # to burn, so I can do completely naïve Gaussian elimination without |
| 190 | # worrying about instability. |
| 191 | |
| 192 | # Arguments: |
| 193 | # matrix_in 2-dimensional array of BigFloats, representing a matrix M |
| 194 | # in row-first order, i.e. matrix_in[r,c] represents the |
| 195 | # entry in row r col c. |
| 196 | # vector_in 1-dimensional array of BigFloats, representing a vector V. |
| 197 | # |
| 198 | # Return value: a 1-dimensional array X of BigFloats, satisfying M X = V. |
| 199 | # |
| 200 | # Expects the input to be an invertible square matrix and a vector of |
| 201 | # the corresponding size, on pain of failing an assertion. |
| 202 | function \(matrix_in :: Array{BigFloat,2}, |
| 203 | vector_in :: Array{BigFloat,1}) |
| 204 | # Copy the inputs, because we'll be mutating them as we go. |
| 205 | M = copy(matrix_in) |
| 206 | V = copy(vector_in) |
| 207 | |
| 208 | # Input consistency criteria: matrix is square, and vector has |
| 209 | # length to match. |
| 210 | n = length(V) |
| 211 | @assert(n > 0) |
| 212 | @assert(size(M) == (n,n)) |
| 213 | |
| 214 | @debug("gausselim", "starting, n=", n) |
| 215 | |
| 216 | for i = 1:1:n |
| 217 | # Straightforward Gaussian elimination: find the largest |
| 218 | # non-zero entry in column i (and in a row we haven't sorted |
| 219 | # out already), swap it into row i, scale that row to |
| 220 | # normalise it to 1, then zero out the rest of the column by |
| 221 | # subtracting a multiple of that row from each other row. |
| 222 | |
| 223 | @debug("gausselim", "matrix=", repr(M)) |
| 224 | @debug("gausselim", "vector=", repr(V)) |
| 225 | |
| 226 | # Find the best pivot. |
| 227 | bestrow = 0 |
| 228 | bestval = 0 |
| 229 | for j = i:1:n |
| 230 | if abs(M[j,i]) > bestval |
| 231 | bestrow = j |
| 232 | bestval = M[j,i] |
| 233 | end |
| 234 | end |
| 235 | @assert(bestrow > 0) # make sure we did actually find one |
| 236 | |
| 237 | @debug("gausselim", "bestrow=", bestrow) |
| 238 | |
| 239 | # Swap it into row i. |
| 240 | if bestrow != i |
| 241 | for k = 1:1:n |
| 242 | M[bestrow,k],M[i,k] = M[i,k],M[bestrow,k] |
| 243 | end |
| 244 | V[bestrow],V[i] = V[i],V[bestrow] |
| 245 | end |
| 246 | |
| 247 | # Scale that row so that M[i,i] becomes 1. |
| 248 | divisor = M[i,i] |
| 249 | for k = 1:1:n |
| 250 | M[i,k] = M[i,k] / divisor |
| 251 | end |
| 252 | V[i] = V[i] / divisor |
| 253 | @assert(M[i,i] == 1) |
| 254 | |
| 255 | # Zero out all other entries in column i, by subtracting |
| 256 | # multiples of this row. |
| 257 | for j = 1:1:n |
| 258 | if j != i |
| 259 | factor = M[j,i] |
| 260 | for k = 1:1:n |
| 261 | M[j,k] = M[j,k] - M[i,k] * factor |
| 262 | end |
| 263 | V[j] = V[j] - V[i] * factor |
| 264 | @assert(M[j,i] == 0) |
| 265 | end |
| 266 | end |
| 267 | end |
| 268 | |
| 269 | @debug("gausselim", "matrix=", repr(M)) |
| 270 | @debug("gausselim", "vector=", repr(V)) |
| 271 | @debug("gausselim", "done!") |
| 272 | |
| 273 | # Now we're done: M is the identity matrix, so the equation Mx=V |
| 274 | # becomes just x=V, i.e. V is already exactly the vector we want |
| 275 | # to return. |
| 276 | return V |
| 277 | end |
| 278 | |
| 279 | # ---------------------------------------------------------------------- |
| 280 | # Least-squares fitting of a rational function to a set of (x,y) |
| 281 | # points. |
| 282 | # |
| 283 | # We use this to get an initial starting point for the Remez |
| 284 | # iteration. Therefore, it doesn't really need to be particularly |
| 285 | # accurate; it only needs to be good enough to wiggle back and forth |
| 286 | # across the target function the right number of times (so as to give |
| 287 | # enough error extrema to start optimising from) and not have any |
| 288 | # poles in the target interval. |
| 289 | # |
| 290 | # Least-squares fitting of a _polynomial_ is actually a sensible thing |
| 291 | # to do, and minimises the rms error. Doing the following trick with a |
| 292 | # rational function P/Q is less sensible, because it cannot be made to |
| 293 | # minimise the error function (P/Q-f)^2 that you actually wanted; |
| 294 | # instead it minimises (P-fQ)^2. But that should be good enough to |
| 295 | # have the properties described above. |
| 296 | # |
| 297 | # Some theory: suppose you're trying to choose a set of parameters a_i |
| 298 | # so as to minimise the sum of squares of some error function E_i. |
| 299 | # Basic calculus says, if you do this in one variable, just |
| 300 | # differentiate and solve for zero. In this case, that works fine even |
| 301 | # with multiple variables, because you _partially_ differentiate with |
| 302 | # respect to each a_i, giving a system of equations, and that system |
| 303 | # turns out to be linear so we just solve it as a matrix. |
| 304 | # |
| 305 | # In this case, our parameters are the coefficients of P and Q; to |
| 306 | # avoid underdetermining the system we'll fix Q's constant term at 1, |
| 307 | # so that our error function (as described above) is |
| 308 | # |
| 309 | # E = \sum (p_0 + p_1 x + ... + p_n x^n - y - y q_1 x - ... - y q_d x^d)^2 |
| 310 | # |
| 311 | # where the sum is over all (x,y) coordinate pairs. Setting dE/dp_j=0 |
| 312 | # (for each j) gives an equation of the form |
| 313 | # |
| 314 | # 0 = \sum 2(p_0 + p_1 x + ... + p_n x^n - y - y q_1 x - ... - y q_d x^d) x^j |
| 315 | # |
| 316 | # and setting dE/dq_j=0 gives one of the form |
| 317 | # |
| 318 | # 0 = \sum 2(p_0 + p_1 x + ... + p_n x^n - y - y q_1 x - ... - y q_d x^d) y x^j |
| 319 | # |
| 320 | # And both of those row types, treated as multivariate linear |
| 321 | # equations in the p,q values, have each coefficient being a value of |
| 322 | # the form \sum x^i, \sum y x^i or \sum y^2 x^i, for various i. (Times |
| 323 | # a factor of 2, but we can throw that away.) So we can go through the |
| 324 | # list of input coordinates summing all of those things, and then we |
| 325 | # have enough information to construct our matrix and solve it |
| 326 | # straight off for the rational function coefficients. |
| 327 | |
| 328 | # Arguments: |
| 329 | # f The function to be approximated. Maps BigFloat -> BigFloat. |
| 330 | # xvals Array of BigFloats, giving the list of x-coordinates at which |
| 331 | # to evaluate f. |
| 332 | # n Degree of the numerator polynomial of the desired rational |
| 333 | # function. |
| 334 | # d Degree of the denominator polynomial of the desired rational |
| 335 | # function. |
| 336 | # w Error-weighting function. Takes two BigFloat arguments x,y |
| 337 | # and returns a scaling factor for the error at that location. |
| 338 | # A larger value indicates that the error should be given |
| 339 | # greater weight in the square sum we try to minimise. |
| 340 | # If unspecified, defaults to giving everything the same weight. |
| 341 | # |
| 342 | # Return values: a pair of arrays of BigFloats (N,D) giving the |
| 343 | # coefficients of the returned rational function. N has size n+1; D |
| 344 | # has size d+1. Both start with the constant term, i.e. N[i] is the |
| 345 | # coefficient of x^(i-1) (because Julia arrays are 1-based). D[1] will |
| 346 | # be 1. |
| 347 | function ratfn_leastsquares(f::Function, xvals::Array{BigFloat}, n, d, |
| 348 | w = (x,y)->BigFloat(1)) |
| 349 | # Accumulate sums of x^i y^j, for j={0,1,2} and a range of x. |
| 350 | # Again because Julia arrays are 1-based, we'll have sums[i,j] |
| 351 | # being the sum of x^(i-1) y^(j-1). |
| 352 | maxpow = max(n,d) * 2 + 1 |
| 353 | sums = zeros(BigFloat, maxpow, 3) |
| 354 | for x = xvals |
| 355 | y = f(x) |
| 356 | weight = w(x,y) |
| 357 | for i = 1:1:maxpow |
| 358 | for j = 1:1:3 |
| 359 | sums[i,j] += x^(i-1) * y^(j-1) * weight |
| 360 | end |
| 361 | end |
| 362 | end |
| 363 | |
| 364 | @debug("leastsquares", "sums=", repr(sums)) |
| 365 | |
| 366 | # Build the matrix. We're solving n+d+1 equations in n+d+1 |
| 367 | # unknowns. (We actually have to return n+d+2 coefficients, but |
| 368 | # one of them is hardwired to 1.) |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 369 | matrix = array2d(BigFloat, n+d+1, n+d+1) |
| 370 | vector = array1d(BigFloat, n+d+1) |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 371 | for i = 0:1:n |
| 372 | # Equation obtained by differentiating with respect to p_i, |
| 373 | # i.e. the numerator coefficient of x^i. |
| 374 | row = 1+i |
| 375 | for j = 0:1:n |
| 376 | matrix[row, 1+j] = sums[1+i+j, 1] |
| 377 | end |
| 378 | for j = 1:1:d |
| 379 | matrix[row, 1+n+j] = -sums[1+i+j, 2] |
| 380 | end |
| 381 | vector[row] = sums[1+i, 2] |
| 382 | end |
| 383 | for i = 1:1:d |
| 384 | # Equation obtained by differentiating with respect to q_i, |
| 385 | # i.e. the denominator coefficient of x^i. |
| 386 | row = 1+n+i |
| 387 | for j = 0:1:n |
| 388 | matrix[row, 1+j] = sums[1+i+j, 2] |
| 389 | end |
| 390 | for j = 1:1:d |
| 391 | matrix[row, 1+n+j] = -sums[1+i+j, 3] |
| 392 | end |
| 393 | vector[row] = sums[1+i, 3] |
| 394 | end |
| 395 | |
| 396 | @debug("leastsquares", "matrix=", repr(matrix)) |
| 397 | @debug("leastsquares", "vector=", repr(vector)) |
| 398 | |
| 399 | # Solve the matrix equation. |
| 400 | all_coeffs = matrix \ vector |
| 401 | |
| 402 | @debug("leastsquares", "all_coeffs=", repr(all_coeffs)) |
| 403 | |
| 404 | # And marshal the results into two separate polynomial vectors to |
| 405 | # return. |
| 406 | ncoeffs = all_coeffs[1:n+1] |
| 407 | dcoeffs = vcat([1], all_coeffs[n+2:n+d+1]) |
| 408 | return (ncoeffs, dcoeffs) |
| 409 | end |
| 410 | |
| 411 | # ---------------------------------------------------------------------- |
| 412 | # Golden-section search to find a maximum of a function. |
| 413 | |
| 414 | # Arguments: |
| 415 | # f Function to be maximised/minimised. Maps BigFloat -> BigFloat. |
| 416 | # a,b,c BigFloats bracketing a maximum of the function. |
| 417 | # |
| 418 | # Expects: |
| 419 | # a,b,c are in order (either a<=b<=c or c<=b<=a) |
| 420 | # a != c (but b can equal one or the other if it wants to) |
| 421 | # f(a) <= f(b) >= f(c) |
| 422 | # |
| 423 | # Return value is an (x,y) pair of BigFloats giving the extremal input |
| 424 | # and output. (That is, y=f(x).) |
| 425 | function goldensection(f::Function, a::BigFloat, b::BigFloat, c::BigFloat) |
| 426 | # Decide on a 'good enough' threshold. |
| 427 | threshold = abs(c-a) * 2^(-epsbits/2) |
| 428 | |
| 429 | # We'll need the golden ratio phi, of course. Or rather, in this |
| 430 | # case, we need 1/phi = 0.618... |
| 431 | one_over_phi = 2 / (1 + sqrt(BigFloat(5))) |
| 432 | |
| 433 | # Flip round the interval endpoints so that the interval [a,b] is |
| 434 | # at least as large as [b,c]. (Then we can always pick our new |
| 435 | # point in [a,b] without having to handle lots of special cases.) |
| 436 | if abs(b-a) < abs(c-a) |
| 437 | a, c = c, a |
| 438 | end |
| 439 | |
| 440 | # Evaluate the function at the initial points. |
| 441 | fa = f(a) |
| 442 | fb = f(b) |
| 443 | fc = f(c) |
| 444 | |
| 445 | @debug("goldensection", "starting") |
| 446 | |
| 447 | while abs(c-a) > threshold |
| 448 | @debug("goldensection", "a: ", a, " -> ", fa) |
| 449 | @debug("goldensection", "b: ", b, " -> ", fb) |
| 450 | @debug("goldensection", "c: ", c, " -> ", fc) |
| 451 | |
| 452 | # Check invariants. |
| 453 | @assert(a <= b <= c || c <= b <= a) |
| 454 | @assert(fa <= fb >= fc) |
| 455 | |
| 456 | # Subdivide the larger of the intervals [a,b] and [b,c]. We've |
| 457 | # arranged that this is always [a,b], for simplicity. |
| 458 | d = a + (b-a) * one_over_phi |
| 459 | |
| 460 | # Now we have an interval looking like this (possibly |
| 461 | # reversed): |
| 462 | # |
| 463 | # a d b c |
| 464 | # |
| 465 | # and we know f(b) is bigger than either f(a) or f(c). We have |
| 466 | # two cases: either f(d) > f(b), or vice versa. In either |
| 467 | # case, we can narrow to an interval of 1/phi the size, and |
| 468 | # still satisfy all our invariants (three ordered points, |
| 469 | # [a,b] at least the width of [b,c], f(a)<=f(b)>=f(c)). |
| 470 | fd = f(d) |
| 471 | @debug("goldensection", "d: ", d, " -> ", fd) |
| 472 | if fd > fb |
| 473 | a, b, c = a, d, b |
| 474 | fa, fb, fc = fa, fd, fb |
| 475 | @debug("goldensection", "adb case") |
| 476 | else |
| 477 | a, b, c = c, b, d |
| 478 | fa, fb, fc = fc, fb, fd |
| 479 | @debug("goldensection", "cbd case") |
| 480 | end |
| 481 | end |
| 482 | |
| 483 | @debug("goldensection", "done: ", b, " -> ", fb) |
| 484 | return (b, fb) |
| 485 | end |
| 486 | |
| 487 | # ---------------------------------------------------------------------- |
| 488 | # Find the extrema of a function within a given interval. |
| 489 | |
| 490 | # Arguments: |
| 491 | # f The function to be approximated. Maps BigFloat -> BigFloat. |
| 492 | # grid A set of points at which to evaluate f. Must be high enough |
| 493 | # resolution to make extrema obvious. |
| 494 | # |
| 495 | # Returns an array of (x,y) pairs of BigFloats, with each x,y giving |
| 496 | # the extremum location and its value (i.e. y=f(x)). |
| 497 | function find_extrema(f::Function, grid::Array{BigFloat}) |
| 498 | len = length(grid) |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 499 | extrema = array1d(Tuple{BigFloat, BigFloat}, 0) |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 500 | for i = 1:1:len |
| 501 | # We have to provide goldensection() with three points |
| 502 | # bracketing the extremum. If the extremum is at one end of |
| 503 | # the interval, then the only way we can do that is to set two |
| 504 | # of the points equal (which goldensection() will cope with). |
| 505 | prev = max(1, i-1) |
| 506 | next = min(i+1, len) |
| 507 | |
| 508 | # Find our three pairs of (x,y) coordinates. |
| 509 | xp, xi, xn = grid[prev], grid[i], grid[next] |
| 510 | yp, yi, yn = f(xp), f(xi), f(xn) |
| 511 | |
| 512 | # See if they look like an extremum, and if so, ask |
| 513 | # goldensection() to give a more exact location for it. |
| 514 | if yp <= yi >= yn |
| 515 | push!(extrema, goldensection(f, xp, xi, xn)) |
| 516 | elseif yp >= yi <= yn |
| 517 | x, y = goldensection(x->-f(x), xp, xi, xn) |
| 518 | push!(extrema, (x, -y)) |
| 519 | end |
| 520 | end |
| 521 | return extrema |
| 522 | end |
| 523 | |
| 524 | # ---------------------------------------------------------------------- |
| 525 | # Winnow a list of a function's extrema to give a subsequence of a |
| 526 | # specified length, with the extrema in the subsequence alternating |
| 527 | # signs, and with the smallest absolute value of an extremum in the |
| 528 | # subsequence as large as possible. |
| 529 | # |
| 530 | # We do this using a dynamic-programming approach. We work along the |
| 531 | # provided array of extrema, and at all times, we track the best set |
| 532 | # of extrema we have so far seen for each possible (length, sign of |
| 533 | # last extremum) pair. Each new extremum is evaluated to see whether |
| 534 | # it can be added to any previously seen best subsequence to make a |
| 535 | # new subsequence that beats the previous record holder in its slot. |
| 536 | |
| 537 | # Arguments: |
| 538 | # extrema An array of (x,y) pairs of BigFloats giving the input extrema. |
| 539 | # n Number of extrema required as output. |
| 540 | # |
| 541 | # Returns a new array of (x,y) pairs which is a subsequence of the |
| 542 | # original sequence. (So, in particular, if the input was sorted by x |
| 543 | # then so will the output be.) |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 544 | function winnow_extrema(extrema::Array{Tuple{BigFloat,BigFloat}}, n) |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 545 | # best[i,j] gives the best sequence so far of length i and with |
| 546 | # sign j (where signs are coded as 1=positive, 2=negative), in the |
| 547 | # form of a tuple (cost, actual array of x,y pairs). |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 548 | best = fill((BigFloat(0), array1d(Tuple{BigFloat,BigFloat}, 0)), n, 2) |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 549 | |
| 550 | for (x,y) = extrema |
| 551 | if y > 0 |
| 552 | sign = 1 |
| 553 | elseif y < 0 |
| 554 | sign = 2 |
| 555 | else |
| 556 | # A zero-valued extremum cannot possibly contribute to any |
| 557 | # optimal sequence, so we simply ignore it! |
| 558 | continue |
| 559 | end |
| 560 | |
| 561 | for i = 1:1:n |
| 562 | # See if we can create a new entry for best[i,sign] by |
| 563 | # appending our current (x,y) to some previous thing. |
| 564 | if i == 1 |
| 565 | # Special case: we don't store a best zero-length |
| 566 | # sequence :-) |
| 567 | candidate = (abs(y), [(x,y)]) |
| 568 | else |
| 569 | othersign = 3-sign # map 1->2 and 2->1 |
| 570 | oldscore, oldlist = best[i-1, othersign] |
| 571 | newscore = min(abs(y), oldscore) |
| 572 | newlist = vcat(oldlist, [(x,y)]) |
| 573 | candidate = (newscore, newlist) |
| 574 | end |
| 575 | # If our new candidate improves on the previous value of |
| 576 | # best[i,sign], then replace it. |
| 577 | if candidate[1] > best[i,sign][1] |
| 578 | best[i,sign] = candidate |
| 579 | end |
| 580 | end |
| 581 | end |
| 582 | |
| 583 | # Our ultimate return value has to be either best[n,1] or |
| 584 | # best[n,2], but it could be either. See which one has the higher |
| 585 | # score. |
| 586 | if best[n,1][1] > best[n,2][1] |
| 587 | ret = best[n,1][2] |
| 588 | else |
| 589 | ret = best[n,2][2] |
| 590 | end |
| 591 | # Make sure we did actually _find_ a good answer. |
| 592 | @assert(length(ret) == n) |
| 593 | return ret |
| 594 | end |
| 595 | |
| 596 | # ---------------------------------------------------------------------- |
| 597 | # Construct a rational-function approximation with equal and |
| 598 | # alternating weighted deviation at a specific set of x-coordinates. |
| 599 | |
| 600 | # Arguments: |
| 601 | # f The function to be approximated. Maps BigFloat -> BigFloat. |
| 602 | # coords An array of BigFloats giving the x-coordinates. There should |
| 603 | # be n+d+2 of them. |
| 604 | # n, d The degrees of the numerator and denominator of the desired |
| 605 | # approximation. |
| 606 | # prev_err A plausible value for the alternating weighted deviation. |
| 607 | # (Required to kickstart a binary search in the nonlinear case; |
| 608 | # see comments below.) |
| 609 | # w Error-weighting function. Takes two BigFloat arguments x,y |
| 610 | # and returns a scaling factor for the error at that location. |
| 611 | # The returned approximation R should have the minimum possible |
| 612 | # maximum value of abs((f(x)-R(x)) * w(x,f(x))). Optional |
| 613 | # parameter, defaulting to the always-return-1 function. |
| 614 | # |
| 615 | # Return values: a pair of arrays of BigFloats (N,D) giving the |
| 616 | # coefficients of the returned rational function. N has size n+1; D |
| 617 | # has size d+1. Both start with the constant term, i.e. N[i] is the |
| 618 | # coefficient of x^(i-1) (because Julia arrays are 1-based). D[1] will |
| 619 | # be 1. |
| 620 | function ratfn_equal_deviation(f::Function, coords::Array{BigFloat}, |
| 621 | n, d, prev_err::BigFloat, |
| 622 | w = (x,y)->BigFloat(1)) |
| 623 | @debug("equaldev", "n=", n, " d=", d, " coords=", repr(coords)) |
| 624 | @assert(length(coords) == n+d+2) |
| 625 | |
| 626 | if d == 0 |
| 627 | # Special case: we're after a polynomial. In this case, we |
| 628 | # have the particularly easy job of just constructing and |
| 629 | # solving a system of n+2 linear equations, to find the n+1 |
| 630 | # coefficients of the polynomial and also the amount of |
| 631 | # deviation at the specified coordinates. Each equation is of |
| 632 | # the form |
| 633 | # |
| 634 | # p_0 x^0 + p_1 x^1 + ... + p_n x^n ± e/w(x) = f(x) |
| 635 | # |
| 636 | # in which the p_i and e are the variables, and the powers of |
| 637 | # x and calls to w and f are the coefficients. |
| 638 | |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 639 | matrix = array2d(BigFloat, n+2, n+2) |
| 640 | vector = array1d(BigFloat, n+2) |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 641 | currsign = +1 |
| 642 | for i = 1:1:n+2 |
| 643 | x = coords[i] |
| 644 | for j = 0:1:n |
| 645 | matrix[i,1+j] = x^j |
| 646 | end |
| 647 | y = f(x) |
| 648 | vector[i] = y |
| 649 | matrix[i, n+2] = currsign / w(x,y) |
| 650 | currsign = -currsign |
| 651 | end |
| 652 | |
| 653 | @debug("equaldev", "matrix=", repr(matrix)) |
| 654 | @debug("equaldev", "vector=", repr(vector)) |
| 655 | |
| 656 | outvector = matrix \ vector |
| 657 | |
| 658 | @debug("equaldev", "outvector=", repr(outvector)) |
| 659 | |
| 660 | ncoeffs = outvector[1:n+1] |
| 661 | dcoeffs = [BigFloat(1)] |
| 662 | return ncoeffs, dcoeffs |
| 663 | else |
| 664 | # For a nontrivial rational function, the system of equations |
| 665 | # we need to solve becomes nonlinear, because each equation |
| 666 | # now takes the form |
| 667 | # |
| 668 | # p_0 x^0 + p_1 x^1 + ... + p_n x^n |
| 669 | # --------------------------------- ± e/w(x) = f(x) |
| 670 | # x^0 + q_1 x^1 + ... + q_d x^d |
| 671 | # |
| 672 | # and multiplying up by the denominator gives you a lot of |
| 673 | # terms containing e × q_i. So we can't do this the really |
| 674 | # easy way using a matrix equation as above. |
| 675 | # |
| 676 | # Fortunately, this is a fairly easy kind of nonlinear system. |
| 677 | # The equations all become linear if you switch to treating e |
| 678 | # as a constant, so a reasonably sensible approach is to pick |
| 679 | # a candidate value of e, solve all but one of the equations |
| 680 | # for the remaining unknowns, and then see what the error |
| 681 | # turns out to be in the final equation. The Chebyshev |
| 682 | # alternation theorem guarantees that that error in the last |
| 683 | # equation will be anti-monotonic in the input e, so we can |
| 684 | # just binary-search until we get the two as close to equal as |
| 685 | # we need them. |
| 686 | |
| 687 | function try_e(e) |
| 688 | # Try a given value of e, derive the coefficients of the |
| 689 | # resulting rational function by setting up equations |
| 690 | # based on the first n+d+1 of the n+d+2 coordinates, and |
| 691 | # see what the error turns out to be at the final |
| 692 | # coordinate. |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 693 | matrix = array2d(BigFloat, n+d+1, n+d+1) |
| 694 | vector = array1d(BigFloat, n+d+1) |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 695 | currsign = +1 |
| 696 | for i = 1:1:n+d+1 |
| 697 | x = coords[i] |
| 698 | y = f(x) |
| 699 | y_adj = y - currsign * e / w(x,y) |
| 700 | for j = 0:1:n |
| 701 | matrix[i,1+j] = x^j |
| 702 | end |
| 703 | for j = 1:1:d |
| 704 | matrix[i,1+n+j] = -x^j * y_adj |
| 705 | end |
| 706 | vector[i] = y_adj |
| 707 | currsign = -currsign |
| 708 | end |
| 709 | |
| 710 | @debug("equaldev", "trying e=", e) |
| 711 | @debug("equaldev", "matrix=", repr(matrix)) |
| 712 | @debug("equaldev", "vector=", repr(vector)) |
| 713 | |
| 714 | outvector = matrix \ vector |
| 715 | |
| 716 | @debug("equaldev", "outvector=", repr(outvector)) |
| 717 | |
| 718 | ncoeffs = outvector[1:n+1] |
| 719 | dcoeffs = vcat([BigFloat(1)], outvector[n+2:n+d+1]) |
| 720 | |
| 721 | x = coords[n+d+2] |
| 722 | y = f(x) |
| 723 | last_e = (ratfn_eval(ncoeffs, dcoeffs, x) - y) * w(x,y) * -currsign |
| 724 | |
| 725 | @debug("equaldev", "last e=", last_e) |
| 726 | |
| 727 | return ncoeffs, dcoeffs, last_e |
| 728 | end |
| 729 | |
| 730 | threshold = 2^(-epsbits/2) # convergence threshold |
| 731 | |
| 732 | # Start by trying our previous iteration's error value. This |
| 733 | # value (e0) will be one end of our binary-search interval, |
| 734 | # and whatever it caused the last point's error to be, that |
| 735 | # (e1) will be the other end. |
| 736 | e0 = prev_err |
| 737 | @debug("equaldev", "e0 = ", e0) |
| 738 | nc, dc, e1 = try_e(e0) |
| 739 | @debug("equaldev", "e1 = ", e1) |
| 740 | if abs(e1-e0) <= threshold |
| 741 | # If we're _really_ lucky, we hit the error right on the |
| 742 | # nose just by doing that! |
| 743 | return nc, dc |
| 744 | end |
| 745 | s = sign(e1-e0) |
| 746 | @debug("equaldev", "s = ", s) |
| 747 | |
| 748 | # Verify by assertion that trying our other interval endpoint |
| 749 | # e1 gives a value that's wrong in the other direction. |
| 750 | # (Otherwise our binary search won't get a sensible answer at |
| 751 | # all.) |
| 752 | nc, dc, e2 = try_e(e1) |
| 753 | @debug("equaldev", "e2 = ", e2) |
| 754 | @assert(sign(e2-e1) == -s) |
| 755 | |
| 756 | # Now binary-search until our two endpoints narrow enough. |
| 757 | local emid |
| 758 | while abs(e1-e0) > threshold |
| 759 | emid = (e1+e0)/2 |
| 760 | nc, dc, enew = try_e(emid) |
| 761 | if sign(enew-emid) == s |
| 762 | e0 = emid |
| 763 | else |
| 764 | e1 = emid |
| 765 | end |
| 766 | end |
| 767 | |
| 768 | @debug("equaldev", "final e=", emid) |
| 769 | return nc, dc |
| 770 | end |
| 771 | end |
| 772 | |
| 773 | # ---------------------------------------------------------------------- |
| 774 | # Top-level function to find a minimax rational-function approximation. |
| 775 | |
| 776 | # Arguments: |
| 777 | # f The function to be approximated. Maps BigFloat -> BigFloat. |
| 778 | # interval A pair of BigFloats giving the endpoints of the interval |
| 779 | # (in either order) on which to approximate f. |
| 780 | # n, d The degrees of the numerator and denominator of the desired |
| 781 | # approximation. |
| 782 | # w Error-weighting function. Takes two BigFloat arguments x,y |
| 783 | # and returns a scaling factor for the error at that location. |
| 784 | # The returned approximation R should have the minimum possible |
| 785 | # maximum value of abs((f(x)-R(x)) * w(x,f(x))). Optional |
| 786 | # parameter, defaulting to the always-return-1 function. |
| 787 | # |
| 788 | # Return values: a tuple (N,D,E,X), where |
| 789 | |
| 790 | # N,D A pair of arrays of BigFloats giving the coefficients |
| 791 | # of the returned rational function. N has size n+1; D |
| 792 | # has size d+1. Both start with the constant term, i.e. |
| 793 | # N[i] is the coefficient of x^(i-1) (because Julia |
| 794 | # arrays are 1-based). D[1] will be 1. |
| 795 | # E The maximum weighted error (BigFloat). |
| 796 | # X An array of pairs of BigFloats giving the locations of n+2 |
| 797 | # points and the weighted error at each of those points. The |
| 798 | # weighted error values will have alternating signs, which |
| 799 | # means that the Chebyshev alternation theorem guarantees |
| 800 | # that any other function of the same degree must exceed |
| 801 | # the error of this one at at least one of those points. |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 802 | function ratfn_minimax(f::Function, interval::Tuple{BigFloat,BigFloat}, n, d, |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 803 | w = (x,y)->BigFloat(1)) |
| 804 | # We start off by finding a least-squares approximation. This |
| 805 | # doesn't need to be perfect, but if we can get it reasonably good |
| 806 | # then it'll save iterations in the refining stage. |
| 807 | # |
| 808 | # Least-squares approximations tend to look nicer in a minimax |
| 809 | # sense if you evaluate the function at a big pile of Chebyshev |
| 810 | # nodes rather than uniformly spaced points. These values will |
| 811 | # also make a good grid to use for the initial search for error |
| 812 | # extrema, so we'll keep them around for that reason too. |
| 813 | |
| 814 | # Construct the grid. |
| 815 | lo, hi = minimum(interval), maximum(interval) |
| 816 | local grid |
| 817 | let |
| 818 | mid = (hi+lo)/2 |
| 819 | halfwid = (hi-lo)/2 |
| 820 | nnodes = 16 * (n+d+1) |
| 821 | pi = 2*asin(BigFloat(1)) |
| 822 | grid = [ mid - halfwid * cos(pi*i/nnodes) for i=0:1:nnodes ] |
| 823 | end |
| 824 | |
| 825 | # Find the initial least-squares approximation. |
| 826 | (nc, dc) = ratfn_leastsquares(f, grid, n, d, w) |
| 827 | @debug("minimax", "initial leastsquares approx = ", |
| 828 | ratfn_to_string(nc, dc)) |
| 829 | |
| 830 | # Threshold of convergence. We stop when the relative difference |
| 831 | # between the min and max (winnowed) error extrema is less than |
| 832 | # this. |
| 833 | # |
| 834 | # This is set to the cube root of machine epsilon on a more or |
| 835 | # less empirical basis, because the rational-function case will |
| 836 | # not converge reliably if you set it to only the square root. |
| 837 | # (Repeatable by using the --test mode.) On the assumption that |
| 838 | # input and output error in each iteration can be expected to be |
| 839 | # related by a simple power law (because it'll just be down to how |
| 840 | # many leading terms of a Taylor series are zero), the cube root |
| 841 | # was the next thing to try. |
| 842 | threshold = 2^(-epsbits/3) |
| 843 | |
| 844 | # Main loop. |
| 845 | while true |
| 846 | # Find all the error extrema we can. |
| 847 | function compute_error(x) |
| 848 | real_y = f(x) |
| 849 | approx_y = ratfn_eval(nc, dc, x) |
| 850 | return (approx_y - real_y) * w(x, real_y) |
| 851 | end |
| 852 | extrema = find_extrema(compute_error, grid) |
| 853 | @debug("minimax", "all extrema = ", format_xylist(extrema)) |
| 854 | |
| 855 | # Winnow the extrema down to the right number, and ensure they |
| 856 | # have alternating sign. |
| 857 | extrema = winnow_extrema(extrema, n+d+2) |
| 858 | @debug("minimax", "winnowed extrema = ", format_xylist(extrema)) |
| 859 | |
| 860 | # See if we've finished. |
| 861 | min_err = minimum([abs(y) for (x,y) = extrema]) |
| 862 | max_err = maximum([abs(y) for (x,y) = extrema]) |
| 863 | variation = (max_err - min_err) / max_err |
| 864 | @debug("minimax", "extremum variation = ", variation) |
| 865 | if variation < threshold |
| 866 | @debug("minimax", "done!") |
| 867 | return nc, dc, max_err, extrema |
| 868 | end |
| 869 | |
| 870 | # If not, refine our function by equalising the error at the |
| 871 | # extrema points, and go round again. |
| 872 | (nc, dc) = ratfn_equal_deviation(f, map(x->x[1], extrema), |
| 873 | n, d, max_err, w) |
| 874 | @debug("minimax", "refined approx = ", ratfn_to_string(nc, dc)) |
| 875 | end |
| 876 | end |
| 877 | |
| 878 | # ---------------------------------------------------------------------- |
| 879 | # Check if a polynomial is well-conditioned for accurate evaluation in |
| 880 | # a given interval by Horner's rule. |
| 881 | # |
| 882 | # This is true if at every step where Horner's rule computes |
| 883 | # (coefficient + x*value_so_far), the constant coefficient you're |
| 884 | # adding on is of larger magnitude than the x*value_so_far operand. |
| 885 | # And this has to be true for every x in the interval. |
| 886 | # |
| 887 | # Arguments: |
| 888 | # coeffs The coefficients of the polynomial under test. Starts with |
| 889 | # the constant term, i.e. coeffs[i] is the coefficient of |
| 890 | # x^(i-1) (because Julia arrays are 1-based). |
| 891 | # lo, hi The bounds of the interval. |
| 892 | # |
| 893 | # Return value: the largest ratio (x*value_so_far / coefficient), at |
| 894 | # any step of evaluation, for any x in the interval. If this is less |
| 895 | # than 1, the polynomial is at least somewhat well-conditioned; |
| 896 | # ideally you want it to be more like 1/8 or 1/16 or so, so that the |
| 897 | # relative rounding error accumulated at each step are reduced by |
| 898 | # several factors of 2 when the next coefficient is added on. |
| 899 | |
| 900 | function wellcond(coeffs, lo, hi) |
| 901 | x = max(abs(lo), abs(hi)) |
| 902 | worst = 0 |
| 903 | so_far = 0 |
| 904 | for i = length(coeffs):-1:1 |
| 905 | coeff = abs(coeffs[i]) |
| 906 | so_far *= x |
| 907 | if coeff != 0 |
| 908 | thisval = so_far / coeff |
| 909 | worst = max(worst, thisval) |
| 910 | so_far += coeff |
| 911 | end |
| 912 | end |
| 913 | return worst |
| 914 | end |
| 915 | |
| 916 | # ---------------------------------------------------------------------- |
| 917 | # Small set of unit tests. |
| 918 | |
| 919 | function test() |
| 920 | passes = 0 |
| 921 | fails = 0 |
| 922 | |
| 923 | function approx_eq(x, y, limit=1e-6) |
| 924 | return abs(x - y) < limit |
| 925 | end |
| 926 | |
| 927 | function test(condition) |
| 928 | if condition |
| 929 | passes += 1 |
| 930 | else |
| 931 | println("fail") |
| 932 | fails += 1 |
| 933 | end |
| 934 | end |
| 935 | |
| 936 | # Test Gaussian elimination. |
| 937 | println("Gaussian test 1:") |
| 938 | m = BigFloat[1 1 2; 3 5 8; 13 34 21] |
| 939 | v = BigFloat[1, -1, 2] |
| 940 | ret = m \ v |
| 941 | println(" ",repr(ret)) |
| 942 | test(approx_eq(ret[1], 109/26)) |
| 943 | test(approx_eq(ret[2], -105/130)) |
| 944 | test(approx_eq(ret[3], -31/26)) |
| 945 | |
| 946 | # Test leastsquares rational functions. |
| 947 | println("Leastsquares test 1:") |
| 948 | n = 10000 |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 949 | a = array1d(BigFloat, n+1) |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 950 | for i = 0:1:n |
| 951 | a[1+i] = i/BigFloat(n) |
| 952 | end |
| 953 | (nc, dc) = ratfn_leastsquares(x->exp(x), a, 2, 2) |
| 954 | println(" ",ratfn_to_string(nc, dc)) |
| 955 | for x = a |
| 956 | test(approx_eq(exp(x), ratfn_eval(nc, dc, x), 1e-4)) |
| 957 | end |
| 958 | |
| 959 | # Test golden section search. |
| 960 | println("Golden section test 1:") |
| 961 | x, y = goldensection(x->sin(x), |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 962 | BigFloat(0), BigFloat(1)/10, BigFloat(4)) |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 963 | println(" ", x, " -> ", y) |
| 964 | test(approx_eq(x, asin(BigFloat(1)))) |
| 965 | test(approx_eq(y, 1)) |
| 966 | |
| 967 | # Test extrema-winnowing algorithm. |
| 968 | println("Winnow test 1:") |
| 969 | extrema = [(x, sin(20*x)*sin(197*x)) |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 970 | for x in BigFloat(0):BigFloat(1)/1000:BigFloat(1)] |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 971 | winnowed = winnow_extrema(extrema, 12) |
| 972 | println(" ret = ", format_xylist(winnowed)) |
| 973 | prevx, prevy = -1, 0 |
| 974 | for (x,y) = winnowed |
| 975 | test(x > prevx) |
| 976 | test(y != 0) |
| 977 | test(prevy * y <= 0) # tolerates initial prevx having no sign |
| 978 | test(abs(y) > 0.9) |
| 979 | prevx, prevy = x, y |
| 980 | end |
| 981 | |
| 982 | # Test actual minimax approximation. |
| 983 | println("Minimax test 1 (polynomial):") |
| 984 | (nc, dc, e, x) = ratfn_minimax(x->exp(x), (BigFloat(0), BigFloat(1)), 4, 0) |
| 985 | println(" ",e) |
| 986 | println(" ",ratfn_to_string(nc, dc)) |
| 987 | test(0 < e < 1e-3) |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 988 | for x = 0:BigFloat(1)/1000:1 |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 989 | test(abs(ratfn_eval(nc, dc, x) - exp(x)) <= e * 1.0000001) |
| 990 | end |
| 991 | |
| 992 | println("Minimax test 2 (rational):") |
| 993 | (nc, dc, e, x) = ratfn_minimax(x->exp(x), (BigFloat(0), BigFloat(1)), 2, 2) |
| 994 | println(" ",e) |
| 995 | println(" ",ratfn_to_string(nc, dc)) |
| 996 | test(0 < e < 1e-3) |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 997 | for x = 0:BigFloat(1)/1000:1 |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 998 | test(abs(ratfn_eval(nc, dc, x) - exp(x)) <= e * 1.0000001) |
| 999 | end |
| 1000 | |
| 1001 | println("Minimax test 3 (polynomial, weighted):") |
| 1002 | (nc, dc, e, x) = ratfn_minimax(x->exp(x), (BigFloat(0), BigFloat(1)), 4, 0, |
| 1003 | (x,y)->1/y) |
| 1004 | println(" ",e) |
| 1005 | println(" ",ratfn_to_string(nc, dc)) |
| 1006 | test(0 < e < 1e-3) |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 1007 | for x = 0:BigFloat(1)/1000:1 |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 1008 | test(abs(ratfn_eval(nc, dc, x) - exp(x))/exp(x) <= e * 1.0000001) |
| 1009 | end |
| 1010 | |
| 1011 | println("Minimax test 4 (rational, weighted):") |
| 1012 | (nc, dc, e, x) = ratfn_minimax(x->exp(x), (BigFloat(0), BigFloat(1)), 2, 2, |
| 1013 | (x,y)->1/y) |
| 1014 | println(" ",e) |
| 1015 | println(" ",ratfn_to_string(nc, dc)) |
| 1016 | test(0 < e < 1e-3) |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 1017 | for x = 0:BigFloat(1)/1000:1 |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 1018 | test(abs(ratfn_eval(nc, dc, x) - exp(x))/exp(x) <= e * 1.0000001) |
| 1019 | end |
| 1020 | |
| 1021 | println("Minimax test 5 (rational, weighted, odd degree):") |
| 1022 | (nc, dc, e, x) = ratfn_minimax(x->exp(x), (BigFloat(0), BigFloat(1)), 2, 1, |
| 1023 | (x,y)->1/y) |
| 1024 | println(" ",e) |
| 1025 | println(" ",ratfn_to_string(nc, dc)) |
| 1026 | test(0 < e < 1e-3) |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 1027 | for x = 0:BigFloat(1)/1000:1 |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 1028 | test(abs(ratfn_eval(nc, dc, x) - exp(x))/exp(x) <= e * 1.0000001) |
| 1029 | end |
| 1030 | |
| 1031 | total = passes + fails |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 1032 | println(passes, " passes ", fails, " fails ", total, " total") |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 1033 | end |
| 1034 | |
| 1035 | # ---------------------------------------------------------------------- |
| 1036 | # Online help. |
| 1037 | function help() |
| 1038 | print(""" |
| 1039 | Usage: |
| 1040 | |
| 1041 | remez.jl [options] <lo> <hi> <n> <d> <expr> [<weight>] |
| 1042 | |
| 1043 | Arguments: |
| 1044 | |
| 1045 | <lo>, <hi> |
| 1046 | |
| 1047 | Bounds of the interval on which to approximate the target |
| 1048 | function. These are parsed and evaluated as Julia expressions, |
| 1049 | so you can write things like '1/BigFloat(6)' to get an |
| 1050 | accurate representation of 1/6, or '4*atan(BigFloat(1))' to |
| 1051 | get pi. (Unfortunately, the obvious 'BigFloat(pi)' doesn't |
| 1052 | work in Julia.) |
| 1053 | |
| 1054 | <n>, <d> |
| 1055 | |
| 1056 | The desired degree of polynomial(s) you want for your |
| 1057 | approximation. These should be non-negative integers. If you |
| 1058 | want a rational function as output, set <n> to the degree of |
| 1059 | the numerator, and <d> the denominator. If you just want an |
| 1060 | ordinary polynomial, set <d> to 0, and <n> to the degree of |
| 1061 | the polynomial you want. |
| 1062 | |
| 1063 | <expr> |
| 1064 | |
| 1065 | A Julia expression giving the function to be approximated on |
| 1066 | the interval. The input value is predefined as 'x' when this |
| 1067 | expression is evaluated, so you should write something along |
| 1068 | the lines of 'sin(x)' or 'sqrt(1+tan(x)^2)' etc. |
| 1069 | |
| 1070 | <weight> |
| 1071 | |
| 1072 | If provided, a Julia expression giving the weighting factor |
| 1073 | for the approximation error. The output polynomial will |
| 1074 | minimise the largest absolute value of (P-f) * w at any point |
| 1075 | in the interval, where P is the value of the polynomial, f is |
| 1076 | the value of the target function given by <expr>, and w is the |
| 1077 | weight given by this function. |
| 1078 | |
| 1079 | When this expression is evaluated, the input value to P and f |
| 1080 | is predefined as 'x', and also the true output value f(x) is |
| 1081 | predefined as 'y'. So you can minimise the relative error by |
| 1082 | simply writing '1/y'. |
| 1083 | |
| 1084 | If the <weight> argument is not provided, the default |
| 1085 | weighting function always returns 1, so that the polynomial |
| 1086 | will minimise the maximum absolute error |P-f|. |
| 1087 | |
| 1088 | Computation options: |
| 1089 | |
| 1090 | --pre=<predef_expr> |
| 1091 | |
| 1092 | Evaluate the Julia expression <predef_expr> before starting |
| 1093 | the computation. This permits you to pre-define variables or |
| 1094 | functions which the Julia expressions in your main arguments |
| 1095 | can refer to. All of <lo>, <hi>, <expr> and <weight> can make |
| 1096 | use of things defined by <predef_expr>. |
| 1097 | |
| 1098 | One internal remez.jl function that you might sometimes find |
| 1099 | useful in this expression is 'goldensection', which finds the |
| 1100 | location and value of a maximum of a function. For example, |
| 1101 | one implementation strategy for the gamma function involves |
| 1102 | translating it to put its unique local minimum at the origin, |
| 1103 | in which case you can write something like this |
| 1104 | |
| 1105 | --pre='(m,my) = goldensection(x -> -gamma(x), |
| 1106 | BigFloat(1), BigFloat(1.5), BigFloat(2))' |
| 1107 | |
| 1108 | to predefine 'm' as the location of gamma's minimum, and 'my' |
| 1109 | as the (negated) value that gamma actually takes at that |
| 1110 | point, i.e. -gamma(m). |
| 1111 | |
| 1112 | (Since 'goldensection' always finds a maximum, we had to |
| 1113 | negate gamma in the input function to make it find a minimum |
| 1114 | instead. Consult the comments in the source for more details |
| 1115 | on the use of this function.) |
| 1116 | |
| 1117 | If you use this option more than once, all the expressions you |
| 1118 | provide will be run in sequence. |
| 1119 | |
| 1120 | --bits=<bits> |
| 1121 | |
| 1122 | Specify the accuracy to which you want the output polynomial, |
| 1123 | in bits. Default 256, which should be more than enough. |
| 1124 | |
| 1125 | --bigfloatbits=<bits> |
| 1126 | |
| 1127 | Turn up the precision used by Julia for its BigFloat |
| 1128 | evaluation. Default is Julia's default (also 256). You might |
| 1129 | want to try setting this higher than the --bits value if the |
| 1130 | algorithm is failing to converge for some reason. |
| 1131 | |
| 1132 | Output options: |
| 1133 | |
| 1134 | --full |
| 1135 | |
| 1136 | Instead of just printing the approximation function itself, |
| 1137 | also print auxiliary information: |
| 1138 | - the locations of the error extrema, and the actual |
| 1139 | (weighted) error at each of those locations |
| 1140 | - the overall maximum error of the function |
| 1141 | - a 'well-conditioning quotient', giving the worst-case ratio |
| 1142 | between any polynomial coefficient and the largest possible |
| 1143 | value of the higher-order terms it will be added to. |
| 1144 | |
| 1145 | The well-conditioning quotient should be less than 1, ideally |
| 1146 | by several factors of two, for accurate evaluation in the |
| 1147 | target precision. If you request a rational function, a |
| 1148 | separate well-conditioning quotient will be printed for the |
| 1149 | numerator and denominator. |
| 1150 | |
| 1151 | Use this option when deciding how wide an interval to |
| 1152 | approximate your function on, and what degree of polynomial |
| 1153 | you need. |
| 1154 | |
| 1155 | --variable=<identifier> |
| 1156 | |
| 1157 | When writing the output polynomial or rational function in its |
| 1158 | usual form as an arithmetic expression, use <identifier> as |
| 1159 | the name of the input variable. Default is 'x'. |
| 1160 | |
| 1161 | --suffix=<suffix> |
| 1162 | |
| 1163 | When writing the output polynomial or rational function in its |
| 1164 | usual form as an arithmetic expression, write <suffix> after |
| 1165 | every floating-point literal. For example, '--suffix=F' will |
| 1166 | generate a C expression in which the coefficients are literals |
| 1167 | of type 'float' rather than 'double'. |
| 1168 | |
| 1169 | --array |
| 1170 | |
| 1171 | Instead of writing the output polynomial as an arithmetic |
| 1172 | expression in Horner's rule form, write out just its |
| 1173 | coefficients, one per line, each with a trailing comma. |
| 1174 | Suitable for pasting into a C array declaration. |
| 1175 | |
| 1176 | This option is not currently supported if the output is a |
| 1177 | rational function, because you'd need two separate arrays for |
| 1178 | the numerator and denominator coefficients and there's no |
| 1179 | obviously right way to provide both of those together. |
| 1180 | |
| 1181 | Debug and test options: |
| 1182 | |
| 1183 | --debug=<facility> |
| 1184 | |
| 1185 | Enable debugging output from various parts of the Remez |
| 1186 | calculation. <facility> should be the name of one of the |
| 1187 | classes of diagnostic output implemented in the program. |
| 1188 | Useful values include 'gausselim', 'leastsquares', |
| 1189 | 'goldensection', 'equaldev', 'minimax'. This is probably |
| 1190 | mostly useful to people debugging problems with the script, so |
| 1191 | consult the source code for more information about what the |
| 1192 | diagnostic output for each of those facilities will be. |
| 1193 | |
| 1194 | If you want diagnostics from more than one facility, specify |
| 1195 | this option multiple times with different arguments. |
| 1196 | |
| 1197 | --test |
| 1198 | |
| 1199 | Run remez.jl's internal test suite. No arguments needed. |
| 1200 | |
| 1201 | Miscellaneous options: |
| 1202 | |
| 1203 | --help |
| 1204 | |
| 1205 | Display this text and exit. No arguments needed. |
| 1206 | |
| 1207 | """) |
| 1208 | end |
| 1209 | |
| 1210 | # ---------------------------------------------------------------------- |
| 1211 | # Main program. |
| 1212 | |
| 1213 | function main() |
| 1214 | nargs = length(argwords) |
| 1215 | if nargs != 5 && nargs != 6 |
| 1216 | error("usage: remez.jl <lo> <hi> <n> <d> <expr> [<weight>]\n" * |
| 1217 | " run 'remez.jl --help' for more help") |
| 1218 | end |
| 1219 | |
| 1220 | for preliminary_command in preliminary_commands |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 1221 | eval(Meta.parse(preliminary_command)) |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 1222 | end |
| 1223 | |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 1224 | lo = BigFloat(eval(Meta.parse(argwords[1]))) |
| 1225 | hi = BigFloat(eval(Meta.parse(argwords[2]))) |
| 1226 | n = parse(Int,argwords[3]) |
| 1227 | d = parse(Int,argwords[4]) |
| 1228 | f = eval(Meta.parse("x -> " * argwords[5])) |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 1229 | |
| 1230 | # Wrap the user-provided function with a function of our own. This |
| 1231 | # arranges to detect silly FP values (inf,nan) early and diagnose |
| 1232 | # them sensibly, and also lets us log all evaluations of the |
| 1233 | # function in case you suspect it's doing the wrong thing at some |
| 1234 | # special-case point. |
| 1235 | function func(x) |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 1236 | y = run(f,x) |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 1237 | @debug("f", x, " -> ", y) |
| 1238 | if !isfinite(y) |
| 1239 | error("f(" * string(x) * ") returned non-finite value " * string(y)) |
| 1240 | end |
| 1241 | return y |
| 1242 | end |
| 1243 | |
| 1244 | if nargs == 6 |
| 1245 | # Wrap the user-provided weight function similarly. |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 1246 | w = eval(Meta.parse("(x,y) -> " * argwords[6])) |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 1247 | function wrapped_weight(x,y) |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 1248 | ww = run(w,x,y) |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 1249 | if !isfinite(ww) |
| 1250 | error("w(" * string(x) * "," * string(y) * |
| 1251 | ") returned non-finite value " * string(ww)) |
| 1252 | end |
| 1253 | return ww |
| 1254 | end |
| 1255 | weight = wrapped_weight |
| 1256 | else |
| 1257 | weight = (x,y)->BigFloat(1) |
| 1258 | end |
| 1259 | |
| 1260 | (nc, dc, e, extrema) = ratfn_minimax(func, (lo, hi), n, d, weight) |
| 1261 | if array_format |
| 1262 | if d == 0 |
| 1263 | functext = join([string(x)*",\n" for x=nc],"") |
| 1264 | else |
| 1265 | # It's unclear how you should best format an array of |
| 1266 | # coefficients for a rational function, so I'll leave |
| 1267 | # implementing this option until I have a use case. |
| 1268 | error("--array unsupported for rational functions") |
| 1269 | end |
| 1270 | else |
| 1271 | functext = ratfn_to_string(nc, dc) * "\n" |
| 1272 | end |
| 1273 | if full_output |
| 1274 | # Print everything you might want to know about the function |
| 1275 | println("extrema = ", format_xylist(extrema)) |
| 1276 | println("maxerror = ", string(e)) |
| 1277 | if length(dc) > 1 |
| 1278 | println("wellconditioning_numerator = ", |
| 1279 | string(wellcond(nc, lo, hi))) |
| 1280 | println("wellconditioning_denominator = ", |
| 1281 | string(wellcond(dc, lo, hi))) |
| 1282 | else |
| 1283 | println("wellconditioning = ", string(wellcond(nc, lo, hi))) |
| 1284 | end |
| 1285 | print("function = ", functext) |
| 1286 | else |
| 1287 | # Just print the text people will want to paste into their code |
| 1288 | print(functext) |
| 1289 | end |
| 1290 | end |
| 1291 | |
| 1292 | # ---------------------------------------------------------------------- |
| 1293 | # Top-level code: parse the argument list and decide what to do. |
| 1294 | |
| 1295 | what_to_do = main |
| 1296 | |
| 1297 | doing_opts = true |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 1298 | argwords = array1d(String, 0) |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 1299 | for arg = ARGS |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 1300 | global doing_opts, what_to_do, argwords |
| 1301 | global full_output, array_format, xvarname, floatsuffix, epsbits |
| 1302 | if doing_opts && startswith(arg, "-") |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 1303 | if arg == "--" |
| 1304 | doing_opts = false |
| 1305 | elseif arg == "--help" |
| 1306 | what_to_do = help |
| 1307 | elseif arg == "--test" |
| 1308 | what_to_do = test |
| 1309 | elseif arg == "--full" |
| 1310 | full_output = true |
| 1311 | elseif arg == "--array" |
| 1312 | array_format = true |
Simon Tatham | 757f906 | 2018-08-15 12:49:56 +0100 | [diff] [blame] | 1313 | elseif startswith(arg, "--debug=") |
| 1314 | enable_debug(arg[length("--debug=")+1:end]) |
| 1315 | elseif startswith(arg, "--variable=") |
| 1316 | xvarname = arg[length("--variable=")+1:end] |
| 1317 | elseif startswith(arg, "--suffix=") |
| 1318 | floatsuffix = arg[length("--suffix=")+1:end] |
| 1319 | elseif startswith(arg, "--bits=") |
| 1320 | epsbits = parse(Int,arg[length("--bits=")+1:end]) |
| 1321 | elseif startswith(arg, "--bigfloatbits=") |
| 1322 | set_bigfloat_precision( |
| 1323 | parse(Int,arg[length("--bigfloatbits=")+1:end])) |
| 1324 | elseif startswith(arg, "--pre=") |
| 1325 | push!(preliminary_commands, arg[length("--pre=")+1:end]) |
George Lander | da55ef9 | 2015-11-19 12:05:06 +0000 | [diff] [blame] | 1326 | else |
| 1327 | error("unrecognised option: ", arg) |
| 1328 | end |
| 1329 | else |
| 1330 | push!(argwords, arg) |
| 1331 | end |
| 1332 | end |
| 1333 | |
| 1334 | what_to_do() |