一覧CS-14-1 · 数値・待ち行列・モデル

ベクトルの内積と行列による座標変換

座標を成分として計算し、回転・拡大の順序によって結果が変わる仕組みを学びます。

説明

ベクトルは複数の成分を順序付きで持つ量です。ここでは平面の位置を(x,y)の二成分で表します。内積は対応する成分同士の積の和で、長さや向きの関係を求めるときに使います。行列は各出力成分を入力成分の重み付き和として計算する規則です。

座標(3,4)の長さと内積を計算した後、90度回転と横方向の2倍拡大を適用します。成分表と文字の座標図で結果を確認し、変換の順序と逆変換まで調べます。

1ベクトルの二つの成分と長さ

原点から右へ3、上へ4の点を使います。ユークリッド長は成分の二乗和の平方根です。

import math平方根; v = (3,4)x・y座標; squared = sum(x*x for x in v)二乗和; v, squared, math.sqrt(squared)座標と長さ

二乗和25、長さ5です。成分を足した7はこの長さではありません。成分の順序を逆にすると位置は(4,3)へ変わります。

2内積と直交

(3,4)に対して(-4,3)の内積を求めます。ゼロでない二つのベクトルの内積0は、向きが直交することを表します。

dot = lambda a,b: sum(x*y for x,y in zip(a,b))対応成分の積の合計; w = (-4,3)直交する向き; dot(v,w), dot(v,v)内積と長さの二乗

3×(-4)+4×3=0です。自分自身との内積25は長さの二乗です。ここでは成分数を2にそろえています。zipは長さが違うと短い方で止まるため、一般の実装では次元の検査も必要です。

3行列の各行が出力を決める

反時計回り90度回転を行列((0,-1),(1,0))で表します。第1行との内積が新x、第2行との内積が新yです。

rotation = ((0,-1),(1,0))90度回転の行列; apply = lambda matrix,vector: tuple(dot(row,vector) for row in matrix)各行と入力の内積; rotated = apply(rotation,v)回転を適用; list(zip(rotation,rotated)), rotated行と出力成分の対応

新x=-4、新y=3となります。列ベクトルとしての入力に左から行列を掛ける約束です。回転後も長さは5で、形を伸ばしてはいません。

4座標を図に配置

Vを元の点、Rを回転後の点、Oを原点として文字の座標図へ置きます。上ほどyが大きくなります。

points = {v:'V',rotated:'R',(0,0):'O'}点の記号; print('\n'.join(f'{y:2d} '+''.join(points.get((x,y), '+' if x == 0 or y == 0 else '.') for x in range(-5,6)) for y in range(5,-2,-1)))上から下へ座標図を表示; print(' x = -5 ... 0 ... 5')横軸の範囲

V=(3,4)からR=(-4,3)へ、原点の周りを反時計回りに90度移動しています。図の軸と成分表が同じ位置を表していることを確認できます。

5横方向だけを2倍にする

拡大行列の対角成分を(2,1)にします。xは2倍、yはそのままです。

scale = ((2,0),(0,1))横2倍・縦1倍; scaled = apply(scale,v)拡大後の座標; scaled, math.sqrt(dot(scaled,scaled))座標と長さ

座標は(6,4)、長さは約7.211です。全方向を一様に2倍する行列ではないため、長さも一律に2倍にはなりません。

6回転と拡大の順序

先に拡大してから回転する場合と、先に回転してから拡大する場合を比べます。

apply(rotation,apply(scale,v))拡大してから回転, apply(scale,apply(rotation,v))回転してから拡大

前者は(-4,6)、後者は(-8,3)です。行列による変換は一般に順序を交換できません。どちらの座標軸に沿って拡大したかが、結果の違いになります。

7二つの変換を一つの行列にする

行列積は左側の各行と右側の各列の内積で求めます。R×Sなら、入力にはSを先に適用します。

multiply = lambda a,b: tuple(tuple(dot(row,col) for col in zip(*b)) for row in a)行と列の内積; combined = multiply(rotation,scale)R×Sを計算; combined, apply(combined,v), apply(rotation,apply(scale,v))合成行列と二方式の結果

合成行列は((0,-1),(2,0))です。一度の行列適用で、拡大後に回転した(-4,6)が得られます。内側から先に適用する式の順序と対応します。

8逆変換と情報の消失

90度回転の逆は時計回り90度です。一方、yを0にする変換では異なる入力が同じ出力になり、一般には元へ戻せません。

inverse_rotation = ((0,1),(-1,0))逆向きの回転; projection = ((1,0),(0,0))yを消す行列; apply(inverse_rotation,rotated)回転を戻す, apply(projection,(3,4)), apply(projection,(3,9))異なる入力が同じ出力になる例

回転は元の(3,4)へ戻ります。射影では(3,4)も(3,9)も(3,0)となるため、出力だけから元のyを特定できません。全ての行列に逆行列が存在するわけではありません。

まとめ

内積は対応成分の積の和で、行列適用では各行が一つの出力成分を決めます。

行列の積は変換の合成に対応し、一般に順序を交換できません。

異なる入力を同じ出力にする変換では、出力だけから元の値を復元できません。

操作履歴と解説を振り返れます。

Python 3

1
import math; v = (3,4); squared = sum(x*x for x in v); v, squared, math.sqrt(squared)
((3, 4), 25, 5.0)
2
dot = lambda a,b: sum(x*y for x,y in zip(a,b)); w = (-4,3); dot(v,w), dot(v,v)
(0, 25)
3
rotation = ((0,-1),(1,0)); apply = lambda matrix,vector: tuple(dot(row,vector) for row in matrix); rotated = apply(rotation,v); list(zip(rotation,rotated)), rotated
([((0, -1), -4), ((1, 0), 3)], (-4, 3))
4
points = {v:'V',rotated:'R',(0,0):'O'}; print('\n'.join(f'{y:2d} '+''.join(points.get((x,y), '+' if x == 0 or y == 0 else '.') for x in range(-5,6)) for y in range(5,-2,-1))); print(' x = -5 ... 0 ... 5')
 5 .....+.....
 4 .....+..V..
 3 .R...+.....
 2 .....+.....
 1 .....+.....
 0 +++++O+++++
-1 .....+.....
   x = -5 ... 0 ... 5
5
scale = ((2,0),(0,1)); scaled = apply(scale,v); scaled, math.sqrt(dot(scaled,scaled))
((6, 4), 7.211102550927978)
6
apply(rotation,apply(scale,v)), apply(scale,apply(rotation,v))
((-4, 6), (-8, 3))
7
multiply = lambda a,b: tuple(tuple(dot(row,col) for col in zip(*b)) for row in a); combined = multiply(rotation,scale); combined, apply(combined,v), apply(rotation,apply(scale,v))
(((0, -1), (2, 0)), (-4, 6), (-4, 6))
8
inverse_rotation = ((0,1),(-1,0)); projection = ((1,0),(0,0)); apply(inverse_rotation,rotated), apply(projection,(3,4)), apply(projection,(3,9))
((3, 4), (3, 0), (3, 0))

演習完了