Linear Algebra

SICP の問題 2.37 を基に図解でわかる線形代数に出てるナニをプログラムしてみる。

とりあえず

2.37 な実装が以下。

(define (accumulate op initial sequence) 
  (if (null? sequence) 
      initial 
      (op (car sequence) 
	  (accumulate op initial (cdr sequence))))) 

(define (accumulate-n op init sequence) 
  (if (null? (car sequence)) 
      '()
      (cons (accumulate op init (map car sequence)) 
	    (accumulate-n op init (map cdr sequence))))) 

(define (dot-product v w)
  (accumulate + 0 (map * v w)))

(define (m-*-v m v) 
  (map (lambda (row-m) (dot-product row-m v)) m))

(define (transpose mat)
  (accumulate-n cons '() mat))

(define (m-*-m m n)
  (let ((cols (transpose n)))
    (map (lambda (row-m)
	   (map (lambda (row-cols)
		  (dot-product row-m row-cols))
		cols))
	 m)))

試験が以下。

(use gauche.test)

(add-load-path ".")
(load "2.37")

(test-start "dot-product")
(test-section "dot-product")
(test* "(dot-product '(0 9) '(2 0))"
       0
       (dot-product '(0 9) '(2 0)))

(test* "(dot-product '(2 0) '(2 0))"
       4
       (dot-product '(2 0) '(2 0)))

(test* "(dot-product '(1 1) '(3 1))"
       4
       (dot-product '(1 1) '(3 1)))

(test-section "m-*-v")
(test* "(m-*-v '((1 2 3) (4 5 6)) '(1 2 3))"
       '(14 32)
       (m-*-v '((1 2 3) (4 5 6)) '(1 2 3)))
(test* "(m-*-v '((1 2) (0 -1)) '(3 4))"
       '(11 -4)
       (m-*-v '((1 2) (0 -1)) '(3 4)))

(test-section "transpose")
(test* "(transpose '((1 2 3) (4 5 6) (7 8 9)))"
       '((1 4 7) (2 5 8) (3 6 9))
       (transpose '((1 2 3) (4 5 6) (7 8 9))))

(test-section "m-*-m")
(test* "(m-*-m '((1 3) (2 4)) '((4 2) (3 1)))"
       '((13 5) (20 8))
       (m-*-m '((1 3) (2 4)) '((4 2) (3 1))))

(test* "(m-*-m '((4 2) (3 1)) '((1 3) (2 4)))"
       '((8 20) (5 13))
       (m-*-m '((4 2) (3 1)) '((1 3) (2 4))))

(test* "(m-*-m '((1 2) (0 -1)) '((3) (4)))"
       '((11) (-4))
       (m-*-m '((1 2) (0 -1)) '((3) (4))))

(test* "(m-*-m '((2 1) (-1 3)) '((0 1) (1 -2)))"
       '((1 0) (3 -7))
       (m-*-m '((2 1) (-1 3)) '((0 1) (1 -2))))

(test-end)

まず trace

試験から書くか。

(test-section "trA")
(test* "(trA '((1 2 3) (4 5 6) (7 8 9)))"
       15
       (trA '((1 2 3) (4 5 6) (7 8 9))))

これ、どーすりゃ良いのやら。R5RS 見たら list-ref って手続きがあるな。以下で試験パスしてるみたい。基本的に正方行列でないと error になるはずですが、これはこれでヨシって事に。

(define (trA l)
  (let trA-inner ((idx 0) (rslt 0) (l l))
    (if (null? l)
	rslt
	(trA-inner (+ 1 idx) (+ rslt (list-ref (car l) idx)) (cdr l))))
  )

2 次正方行列の determinant

ええと試験は以下?

(test-section "detA-2")
(test* "(detA-2 '((1 2) (3 4)))"
       -2
       (detA-2 '((1 2) (3 4))))
(test* "(detA-2 '((2 3) (2 3)))"
       0
       (detA-2 '((2 3) (2 3))))

実装は簡単ですな。でもこんなので良いのだろうか。

(define (detA-2 l)
  (- (* (car (car l)) (cadr (cadr l)))
     (* (car (cadr l)) (cadr (car l)))))

Inverse

逆行列。その前に行列のスカラ倍な手続きが必要ですな。なんて名前にするべきか。

(test-section "scalar-*-m")
(test* "(scalar-*-m 2 '((1 2) (3 4)))"
       '((2 4) (6 8))
       (scalar-*-m 2 '((1 2) (3 4))))

実装が以下で一応試験パス。

(define (scalar-*-m s l)
  (define (row-calc row)
    (if (null? row)
	'()
	(cons (* s (car row)) (row-calc (cdr row)))))
  (let s-*-m-inner ((l l) (rslt '()))
    (if (null? l)
	rslt
	(s-*-m-inner (cdr l)
		     (append rslt (list (row-calc (car l))))))))

なんか微妙だなぁ。こうしてみた。

(define (scalar-*-m s l)
  (define (row-calc row)
    (if (null? row)
	'()
	(cons (* s (car row)) (row-calc (cdr row)))))
  (define (s-*-m-inner l rslt)
    (if (null? l)
	rslt
	(s-*-m-inner (cdr l)
		     (append rslt (list (row-calc (car l)))))))
  (s-*-m-inner l '()))

そーゆー問題ではないな。row-calc が末尾呼び出しでないあたりが気持ち悪い。これ、map 使えば良いのか。ちょい再検討。まず、scalar-*-v を作るか。

(test-section "scalar-*-v")
(test* "(scalar-*-v 2 '(1 2))"
       '(2 4)
       (scalar-*-v 2 '(1 2)))

map 使えば簡単。

(define (scalar-*-v s v)
  (map (lambda (x) (* s x)) v))

これを使う scalar-*-m を作れば良い。ここでも map が使えるはず。

(define (scalar-*-m s m)
  (map (lambda (x) (scalar-*-v s x)) m))

試験パス。いやはや。

これで逆行列の公式を適用する準備ができた訳ですな。detA が 0 じゃなければ云々、とテキストには書いてありますな。まず試験を準備。

(test-section "inverse")
(test* "(inverse '((2 3) (2 3)))"
       #f
       (inverse '((2 3) (2 3))))

(test* "(inverse '((2 5) (1 2)))"
       '((-2 5) (1 -2))
       (inverse '((2 5) (1 2))))

(test* "(inverse '((-2 6) (2 -4)))"
       '((1 3/2) (1/2 1/2))
       (inverse '((-2 6) (2 -4))))

detA が 0 なら #f を戻す方向。実装は以下?

(define (inverse m)
  (let ((detA (detA-2 m)))
    (if (= 0 detA)
	#f
	(scalar-*-m (/ 1 detA) m))))

試験パスしない。定義を良く見たらスカラ倍するマトリクスにも変更があるな。これはどうしたものやら。2 次の正方行列限定で無理矢理以下?

(define (inverse-2 m)
  (let ((detA (detA-2 m))
	(lst (list (list (cadr (cadr m)) (- (cadr (car m))))
		   (list (- (car (cadr m))) (car (car m))))))
    (if (= 0 detA)
	#f
	(scalar-*-m (/ 1 detA) lst))))

これは酷い。試験にはパスしてますが。とりあえずこの時点でエントリ投入。