racket/collects/plot/common/axis-transform.rkt
Neil Toronto 553c72ab28 Moved some flonum stuff (e.g. flatan2, flnext, +max.0, +min.0, etc.) to unstable/flonum (will document in another commit)
Moved Racket-language, doc-generating "defthing" defines to unstable/latent-contract/defthing (will document in another commit)
2011-11-25 18:40:19 -07:00

165 lines
6.5 KiB
Racket

#lang racket/base
(require racket/math racket/flonum racket/contract racket/match unstable/latent-contract/defthing
"math.rkt"
"contract.rkt")
(provide (all-defined-out))
(struct invertible-function (f g) #:transparent)
(defproc (invertible-compose [f1 invertible-function?] [f2 invertible-function?]
) invertible-function?
(match-let ([(invertible-function f1 g1) f1]
[(invertible-function f2 g2) f2])
(invertible-function (compose f1 f2) (compose g2 g1))))
(defproc (invertible-inverse [h invertible-function?]) invertible-function? #:document-body
(match-define (invertible-function f g) h)
(invertible-function g f))
(defcontract axis-transform/c (real? real? invertible-function? . -> . invertible-function?))
(defthing id-transform axis-transform/c
(λ (x-min x-max old-function) old-function))
(defthing id-function invertible-function? (invertible-function (λ (x) x) (λ (x) x)))
(defproc (apply-axis-transform [t axis-transform/c] [x-min real?] [x-max real?]
) invertible-function? #:document-body
(t x-min x-max id-function))
;; Turns any total, surjective, monotone real function and its inverse into an axis transform
(defproc (make-axis-transform [fun invertible-function?]) axis-transform/c
(match-define (invertible-function f g) fun)
(λ (x-min x-max old-function)
(define fx-min (f x-min))
(define fx-scale (/ (- x-max x-min) (- (f x-max) fx-min)))
(define (new-f x) (+ x-min (* (- (f x) fx-min) fx-scale)))
(define (new-g y) (g (+ fx-min (/ (- y x-min) fx-scale))))
(invertible-compose (invertible-function new-f new-g) old-function)))
;; ===================================================================================================
;; Axis transform combinators
(defproc (axis-transform-compose [t1 axis-transform/c] [t2 axis-transform/c]) axis-transform/c
(λ (x-min x-max old-function)
(t1 x-min x-max (t2 x-min x-max old-function))))
(defproc (axis-transform-append [t1 axis-transform/c] [t2 axis-transform/c] [mid real?]
) axis-transform/c
(λ (x-min x-max old-function)
(match-define (invertible-function old-f old-g) old-function)
(let ([mid (old-f mid)])
(cond [(mid . >= . x-max) (t1 x-min x-max old-function)]
[(mid . <= . x-min) (t2 x-min x-max old-function)]
[else
(match-define (invertible-function f1 g1) (t1 x-min mid old-function))
(match-define (invertible-function f2 g2) (t2 mid x-max old-function))
((make-axis-transform
(invertible-function
(λ (x) (cond [((old-f x) . < . mid) (f1 x)]
[else (f2 x)]))
(λ (x) (cond [(x . < . mid) (g1 x)]
[else (g2 x)]))))
x-min x-max id-function)]))))
(defproc (axis-transform-bound [t axis-transform/c] [a real?] [b real?]
) axis-transform/c #:document-body
(axis-transform-append
(axis-transform-append id-transform t a) id-transform b))
;; ===================================================================================================
;; Specific axis transforms
(define ((flnewton-invert f f-diff f-inv-guess n) y)
(let ([y (exact->inexact y)])
(let loop ([x (f-inv-guess y)] [n n])
(let/ec return
(when (zero? n) (return x))
(define dx (fl/ (fl- y (f x)) (f-diff x)))
(when (zero? dx) (return x))
(loop (fl- x dx) (sub1 n))))))
(define (sine-diag d)
(let ([d (exact->inexact d)])
(λ (x) (let ([x (exact->inexact x)])
(fl+ x (fl* (fl/ 1.0 (fl* 4.0 d)) (flsin (fl* d x))))))))
(define (sine-diag-diff d)
(let ([d (exact->inexact d)])
(λ (x) (let ([x (exact->inexact x)])
(fl- (fl/ (flcos (fl* d x)) 4.0) 1.0)))))
(define (sine-diag-inv d)
(flnewton-invert (sine-diag d) (sine-diag-diff d) values 10))
(define cbrt
(let ([e (exact->inexact 1/3)])
(λ (x) (let ([x (exact->inexact x)])
(fl* (sgn x) (expt (flabs x) e))))))
(define (cube x)
(let ([x (exact->inexact x)])
(fl* x (fl* x x))))
(define (real-log x)
(fllog (exact->inexact x)))
(define (real-exp x)
(flexp (exact->inexact x)))
(defthing log-transform axis-transform/c
(λ (x-min x-max old-function)
(when ((exact->inexact x-min) . <= . 0)
(raise-type-error 'log-transform "positive real" 0 x-min x-max))
((make-axis-transform (invertible-function real-log real-exp)) x-min x-max old-function)))
(defthing cbrt-transform axis-transform/c
(λ (x-min x-max old-function)
((make-axis-transform (invertible-function cbrt cube)) x-min x-max old-function)))
(defproc (hand-drawn-transform [freq (>/c 0)]) axis-transform/c
(λ (x-min x-max old-function)
(define d (/ freq (- x-max x-min)))
((make-axis-transform (invertible-function (sine-diag d) (sine-diag-inv d)))
x-min x-max old-function)))
;; ===================================================================================================
(define (stretch a b s)
(define d (- b a))
(define ds (* d s))
(λ (x)
(cond [(x . < . a) x]
[(x . > . b) (+ (- x d) ds)]
[else (+ a (* (- x a) s))])))
(defproc (stretch-transform [a real?] [b real?] [scale (>/c 0)]) axis-transform/c
(when (a . > . b) (error 'stretch-transform "expected a <= b; given ~e and ~e" a b))
(λ (x-min x-max old-function)
(match-define (invertible-function old-f old-g) old-function)
(let ([a (old-f a)]
[b (old-f b)])
(define f (stretch a b scale))
(define g (stretch (f a) (f b) (/ 1 scale)))
((make-axis-transform (invertible-function f g)) x-min x-max old-function))))
(defproc (collapse-transform [a real?] [b real?]) axis-transform/c
(when (a . > . b) (error 'stretch-transform "expected a <= b; given ~e and ~e" a b))
(λ (x-min x-max old-function)
(match-define (invertible-function old-f old-g) old-function)
(let ([a (old-f a)]
[b (old-f b)])
(define 1/2size (* 1/2 (- b a)))
(define center (* 1/2 (+ a b)))
(define (f x) (cond [(x . < . a) (+ x 1/2size)]
[(x . > . b) (- x 1/2size)]
[else center]))
(define (g x) (cond [(x . < . center) (- x 1/2size)]
[(x . > . center) (+ x 1/2size)]
[else center]))
((make-axis-transform (invertible-function f g)) x-min x-max old-function))))