幾何¶
SymPy のサブパッケージ sympy.geometry に関わる覚え書きを記す。
主要クラス図¶
サブパッケージ sympy.geometry が提供する主要クラスの継承関係のみを図示するとこういう感じになる。
クラス
GeometryEntityのスーパークラスはクラスBasicである。
クラス継承関係¶
基本的に二次元幾何。同じ幾何要素で次元が異なるものはクラスも別々に定義がある。例えば
PointとPoint3Dが存在する。ここにあるあらゆるメソッド、関数は 2D と 3D を混ぜて利用することを想定していない。
RegularPolygonis-aPolygonだったりCircleis-aEllipseだったりする。オブジェクト指向プログラミングの本からするとこういう設計はするなと言われそうだが、SymPy は記号数学のライブラリーなのでむしろアリということだろう。
共通機能¶
ここでは便宜上、次のものを共通機能と呼んで理解を深める。
クラス
GeometryEntityのオブジェクトを引数に取る関数クラス
GeometryEntityの静的・クラスメソッドクラス
GeometryEntityのメソッド
クラス GeometryEntity のオブジェクトを引数に取る関数¶
関数
intersection引数は複数個の
GeometryEntityオブジェクト。戻り値は
listで、その要素はPointやSegmentになるのだろう。
関数
convex_hull複数個の
Point,Segment,Polygonオブジェクトを引数に取る。戻り値は基本的には凸包を表現する
Polygonオブジェクトだが、場合によってはSegmentかもしれない。
関数
are_coplanar引数は複数個の
GeometryEntityオブジェクト。引数のすべての 3D オブジェクト全部がある平面上に乗っているかどうかをテストする。
そのような平面が
Planeオブジェクトとして返るわけではないようだ?2D のオブジェクトは一応 3D 化してから計算してくれる。計算するまでもないだろう。
関数
are_similarふたつの
GeometryEntityオブジェクトが相似かどうかをテストする。単に
entity1.is_similar(entity2)よりも気が利いた処理をするに過ぎない。
関数
centroid引数は複数個の
GeometryEntityオブジェクト。おそらく同型でなければならない。
戻り値は
Pointオブジェクトで、オブジェクト全部が決定する重心の座標を表現する。
クラス GeometryEntity の静的・クラスメソッド¶
クラス GeometryEntity の静的・クラスメソッドは存在しない。
クラス GeometryEntity のメソッド¶
メソッド
intersectionは先述の同名関数を参照。メソッド
is_similarは先述の関数are_similarを参照。変形系メソッド
rotate,scale,translate,reflectが提供されている。rotate,scaleは変形の原点を指定できる。reflectにはLinearEntityオブジェクトを渡すようだ。
メソッド
encloses自身のオブジェクトの内側に与えられたオブジェクト全体を含むかどうかをテストする。
実装にはサブクラスのメソッド
encloses_pointを利用している。造りが美しくない。
メソッド
equalsが提供されており、オーバーライドがなければe1 == e2と同値。演算子がいくつか定義されているが、これらの存在はひとまず忘れておく。
サブクラス¶
以下、クラス GeometryEntity の各サブクラスについての感想やら何やらを記す。
クラス
Point座標成分は具体的な数値でもよいし、シンボルでもよい。ただし、いくつかの判定モノの関数は具体的な
Pointで計算しないと意味がなくなる。プロパティー
lengthは必ずゼロを返す。メソッド
is_collinearとis_concyclicはクラスメソッドである。
類似したライブラリーを色々知っているが
is_concyclic的なものは初めてお目にかかる。
メソッド
evalfは各座標を浮動小数点数で表現し直したPointを返す。メソッド
dotでドット積を計算する。演算子で加算、減算、スカラー倍、etc. が実現できる。単項マイナスと絶対値もサポート。
メソッド
transformは行列を右から掛けるようだ。vM 方式。
クラス
Point3Dクラス
Pointにあるものは、だいたいその三次元版メンバーが存在する。クラス
Pointにはない次のメソッドに注意。メソッド
direction_ratio,direction_cosineクラスメソッド
are_collinear,are_coplanar
メソッド
is_collinearとis_concyclicは存在しない。
クラス
LinearEntity二次元空間内のまっすぐな線のためのスーパークラス。
この手のクラスによくあるプロパティー、メソッドがやはりある。
メソッド
parallel_line,perpendicular_lineはLineオブジェクトを生成する。一方、メソッド
perpendicular_segmentはSegmentオブジェクトを生成する。
クラスメソッド
are_concurrent的なものは初めてお目にかかる。すべての引数が「一点で交差する」かどうかをテストする。メソッド
intersectionの対象はPointかLinearEntityに限定している?メソッド
arbitrary_pointで線上の任意の一点を返す。デフォルトでは線のパラメーターシンボルを
tとする。
メソッド
random_pointで線上の勝手な一点を返す。メソッド
is_similarは線の傾きを比較する。メソッド
containsは形状を集合と見たときの包含関係のテストと思ってよい。メソッド
distanceはここにはなく、各サブクラスにある。
クラス
Segment有限の線分を表現する。
メソッド
plot_intervalはlist(t, 0, 1)を返す。メソッド
perpendicular_bisectorの仕様が CAD の感覚だとやや不親切な気がする。指定点が bisector に乗らない場合は、直線ではなくて中点から投影点までの線分を生成するのかと思った。
クラス
Ray半直線を表現する。
メソッド
plot_intervalはlist(t, 0, 10)を返す。プロパティー
xdirection,ydirectionにより、形状がどの座標軸に沿って無限なのかがわかる。
クラス
Line直線を表現する。
メソッド
plot_intervalはlist(t, -5, 5)を返す。こいつだけメソッド
equationを持っていて、直線の式simplify(a*x + b*y + c)を生成する。
クラス
LinearEntity3Dおよびそのサブクラス群がLinearEntity系と同じ階層構造で存在する。クラス
Plane平面を表現する。平面に期待するメンバーが存在する。
平行、垂直のテストは
LinearEntity3DまたはPlaneに対するものだ。メソッド
distanceの対象はPoint3D,LinearEntity3DまたはPlaneのいずれか。メソッド
arbitrary_point,random_pointがある。クラスメソッド
are_concurrentは複数の平面が共通の直線で交わるかどうかをテストする珍しいものだ。メソッド
is_coplanarで平面に乗るかどうかをテスト。
クラス
Polygonxy 平面上の多角形を表現する。
コンストラクターが気を利かせて、別のクラスのオブジェクトを生成することがある。極端な例を挙げると、一直線上に並ぶ任意の点列を
Polygonのコンストラクターに与えると、得られるオブジェクトの型はなんとSegmentである。プロパティー
areaで面積を計算する。符号付きの可能性がある。プロパティー
anglesで内角のlistを返す。プロパティー
perimeterは周長を返す。プロパティー
centroidで重心に位置するPointオブジェクトを生成する。プロパティー
sidesで各辺をSegmentで表現するオブジェクトのlistを返す。メソッド
intersectionの計算はこれに基づく。
メソッド
is_convexでこの多角形が凸かどうかをテストする。メソッド
arbitrary_pointについてt=0で始点t=1で終点
メソッド
plot_intervalはlist(t, 0, 1)を返す。
クラス
Triangle三角形は多角形の中でも別格の存在ということで、専用クラスとして存在するようだ。
コンストラクターは色々な引数指定をサポートしている。使いやすいものを覚えておくこと。
単に三頂点を
Pointオブジェクトで指定する。キーワード引数
sss等と辺の長さ・角度を列挙を組み合わせて指定する。オブジェクトの座標があらかじめ想像しづらい。
メソッド
is_similarの実装は、三辺の比をテストするだけ。辺の順序の組み合わせはすべて考慮する。メソッド
is_equilateralで正三角形テスト。メソッド
is_isoscelesで二等辺三角形テスト。メソッド
is_rightで直角三角形テスト。五心
名前
Point
Circle
何の交点か
外心 (O)
circumcentercircumcirclesides().perpendicular_bisector()垂心 (H)
orthocenterなし
altitudes内心 (I)
incenterincirclebisectors傍心 (J)
なし
なし
なし
重心 (G)
centroidなし
medians
クラス
RegularPolygon正多角形を表現するクラス。
頂点の位置を直接指定してオブジェクトを生成するというよりは、半径と頂点数を指定する。
プロパティー
lengthの実装を見ると非効率的な印象を受けるが、記号数学演算的にはこれしかない。これは
perimeterを利用しない。
プロパティー
apothemおよびinradiusで内接円の半径を得る。プロパティー
interior_angleとexterior_angleで多角形の内角、外角をそれぞれ得る。プロパティー
incircleとcircumcircleで内接円、外接円を Circle オブジェクトとしてそれぞれ得る。コンストラクターおよびメソッド
spinで多角形の中心点を軸に回転をかけられる。
クラス
Ellipseコンストラクターが色々ある。
長軸・短軸ではなく
hradius,vradiusのような扱いをする。
プロパティー
minor,majorで長軸、短軸の半分の長さを得る。プロパティー
circumferenceで楕円の周長が得られる。定積分オブジェクトかもしれない。プロパティー
periapsis,apoapsisで楕円の焦点から近点、焦点から遠点の距離がそれぞれ得られる。メソッド
tangent_linesである点から楕円上に接線を求める。たいていの場合、戻り値は
Lineオブジェクト二個のlistとなる。
メソッド
is_tangentはEllipseにも対応している。メソッド
normal_linesはある点から楕円に垂直に交差するLineを求める。要素数は 1, 2, 4 のいずれか。
キーワード引数
precの存在に注意。
メソッド
plot_intervalはlist(t, -S.Pi, S.Pi)を返す。メソッド
equationで楕円の方程式(の左辺)を得る。メソッド
evoluteで楕円の縮閉線の方程式(の左辺)を得る。
クラス
Circleコンストラクターは次のどちらかの引数リストを受け付ける。
通過点を表現する
Pointオブジェクト三つ。中心を表現する
Pointと半径。
メソッド
scaleでEllipseオブジェクトが生成することがある。
クラス
Curve平面的なパラメトリック曲線を表現するクラス。
コンストラクターの例:
Curve((sin(t), cos(t)), (t, 0, 2))プロパティー
limitsは曲線のパラメーター定義域を表現するtupleである。parameterは曲線のパラメーターのためのシンボルである。functionsは各座標成分の関数のtupleである。
デモ¶
メネラウスの定理¶
#!/usr/bin/env python
"""meneraus.py: Demonstrate Menelaus' theorem with SymPy.
"""
from sympy import (Point, Line, symbols)
def demonstrate_menelaus(l, A, B, C):
"""Demonstrate Menelaus' theorem."""
c, a, b = Line(A, B), Line(B, C), Line(C, A)
P, Q, R = (l.intersection(i)[0] for i in (a, b, c))
print("P=", P.evalf())
print("Q=", Q.evalf())
print("R=", R.evalf())
numer = A.distance(R) * B.distance(P) * C.distance(Q)
denom = R.distance(B) * P.distance(C) * Q.distance(A)
print("numer=", numer.evalf())
print("denom=", denom.evalf())
# Or show simplify(numer/denom).
print((numer/denom).evalf())
if __name__ == '__main__':
if True:
line = Line(Point(0, 0), Point(1, 1))
vertices = (Point(21, 344),
Point(-143, -22),
Point(59, 300),)
else:
line = Line(*[Point(i, j) for i, j in
zip(symbols('X1:3'), symbols('Y1:3'))])
vertices = [Point(i, j) for i, j in
zip(symbols('x1:4'), symbols('y1:4'))]
assert len(vertices) == 3
demonstrate_menelaus(line, *vertices)
本当は数値的でないほうの座標で検証したかったが、私の環境では
simplify(numer/denom)が返って来なかった。具体的な座標を与えた方の検証はうまくいく。
bash$ ./menelaous.py P= Point(-346.683333333333, -346.683333333333) Q= Point(170.682926829268, 170.682926829268) R= Point(-241.237623762376, -241.237623762376) numer= 41999675.9784931 denom= 41999675.9784931 1.00000000000000
チェバの定理¶
#!/usr/bin/env python
"""ceva.py: Demonstrate Ceva's theorem with SymPy.
"""
from sympy import (Point, Line, symbols)
def demonstrate_ceva(O, A, B, C):
"""Demonstrate Ceva's theorem."""
c, a, b = Line(A, B), Line(B, C), Line(C, A)
OA, OB, OC = (Line(O, p) for p in (A, B, C))
P, Q, R = (i.intersection(j)[0] for i, j in
zip((OA, OB, OC), (a, b, c)))
print("P=", P.evalf())
print("Q=", Q.evalf())
print("R=", R.evalf())
numer = A.distance(R) * B.distance(P) * C.distance(Q)
denom = R.distance(B) * P.distance(C) * Q.distance(A)
print("numer=", numer.evalf())
print("denom=", denom.evalf())
# Or show simplify(numer/denom).
print((numer/denom).evalf())
if __name__ == '__main__':
if True:
vertices = (Point(0, 0),
Point(21, 344),
Point(-143, -22),
Point(59, 300),)
else:
vertices = [Point(i, j) for i, j in
zip(symbols('x0:4'), symbols('y0:4'))]
assert len(vertices) == 4
demonstrate_ceva(*vertices)
本当は数値的でないほうの座標で検証したかったが、私の環境では
simplify(numer/denom)が返って来なかった。具体的な座標を与えた方の検証はうまくいく。
bash$ ./ceva.py P= Point(13.9279086822051, 228.152408889456) Q= Point(280.783950617284, 43.1975308641975) R= Point(104.14656234152, 529.558791567051) numer= 20374326.3842940 denom= 20374326.3842940 1.00000000000000
方べきの定理¶
具体的な座標を与えないとどうも上手くいかないようなので、数値計算に切り替えて様子見だ。さらに難易度?を落とし、方べきの定理を再現してみたい。
#!/usr/bin/env python
"""circle_power.py: Demonstrate power-of-a-point theorem with SymPy.
"""
from sympy import (Point, Line, Circle)
def demonstrate_circle_power(circle):
"""Demonstrate power-of-a-point theorem."""
A, B, C, D = (circle.random_point() for i in range(4))
P = Line.intersection(Line(A, B), Line(C, D))
assert len(P) == 1
P = P[0]
PAPB = P.distance(A) * P.distance(B)
PCPD = P.distance(C) * P.distance(D)
print("PA * PB =", PAPB.evalf())
print("PC * PD =", PCPD.evalf())
if __name__ == '__main__':
center = Point(0, 0)
radius = 1
demonstrate_circle_power(Circle(center, radius))
円を単位円に固定する代わり、円周上の四点をランダムにとり、それらを結ぶ二弦の交点に対する方べきの定理を検証しよう。
bash$ ./circle_power.py
PA * PB = 0.245325501000245
PC * PD = 0.245325501000245
bash$ ./circle_power.py
PA * PB = 0.0519999915840500
PC * PD = 0.0519999915840500
Todo
プロットは実現したい。
上の例で
distanceを多用しているが、値一致テストの目的ならdotを用いるのが常識的か。もっとサンプルを作りたい。