はじめに
この記事では、pythonの基本的なプログラムをかけることを前提にN芒星を描くことを目標とします。
目次
- N芒星とは
- N芒星のソースコードを書くにあたって
- ソースコード解説
- 悪魔の数字 '6'
N芒星とは
N芒星という言葉、普段生活してる中では聞き慣れない人も多いかもしれません。N芒星とは、星形多角形のことを言い、多角形(N角形)の各辺を延長して得られた交点を結んだ図形のことを言います。一般的にN芒星はN>=5の領域でのみ満たされ、これは、Nが4以下の領域では辺の延長線上に交点が現れないことに起因します。
N芒星のソースコードを書くにあたって
さて、コードを早速書いていこうと思いますが、まだ慌てずに・・・
今回のコードを書くためには整数論的アプローチが重要となリます。今回は円周上をN等分し、その点を規則的に結んでいくことでN芒星を描くことを考えます。ここで重要となってくるのが、整数nとそれと互いに素である整数k(k < n)を用意すると、『nを法とする剰余類でkを足していくという巡回群が0~n-1の値を全て取る』という考えです。何を言ってるか訳がわからない、という人のために詳しく説明しておきたいと思います。
剰余類とは、整数nを整数kで割った時の剰余が等しい整数をすべて集めたものを言い、巡回群というのは一つの元で生成される群のことを言います。
具体的に見ていきたいと思います。
例えば、nとして7、kとして3を用意します。(ここでは、3と7が互いに素であることに注意しましょう。)
以下に、7を法とする剰余類において3を足していくと言う巡回群を示してみます。
まず、3に3を足して6、さらに3を足して9となります。ここで今回は7を法とする剰余類を考えているので、9は2と見做せます。
以上のような操作を繰り返していくと以下のようになります。
このようにして見てみると、先ほど述べた、整数nとそれと互いに素である整数k(k < n)を用意すると、『nを法とする剰余類でkを足していくという巡回群が0~n-1の値を全て取る』というのが分かるのではないでしょうか。上記の例で言うならば、確かに巡回群は[3,6,2,5,1,4,0]と0~6の値を全て取っていることが分かります。
こんな話をして、これと何がN芒星と関係あんだよと思われる方がいるかもしれません。。。
そんな方は是非とも下の図を見てください !
ただ、これを見て、じゃあ整数nとそれと互いに素である整数k(k < n)を用意すればどんなN芒星も書けるのかと思いがちですので、少し注意してください。
ここで、もう一度N芒星の定義を復習したいと思います。最初にも述べたとおり、N芒星とは多角形(N角形)の各辺を延長して得られた交点を結んだ図形のことを言います。つまり、多角形(N角形)自体をN芒星とは呼ばないのです。
例えば、nとして7、kとして6を用意してみましょう。(6と7は互いに素)
先の例のように、7を法とする剰余類において6を足していくと言う巡回群を考えてみると、[6,5,4,3,2,1,0]となります。これを実際に図に落とし込むと以下のようになります。
N芒星を描くためには、互いに素なn、k(k≠1,n-1)を用意する必要があるのです。
ソースコード解説
まずは、ソースコード全体を以下に示しておきます。
from PIL import Image, ImageDraw
from math import pi, sin, cos
def gcd(k, n):
if (k==0):
return False
elif (k==1):
return k
else:
return gcd(n%k, k)
def djt(k,n):
if gcd(k,n) == 1:
return k
else:
return False
k= "任意の整数"
n= "任意の整数"
k = djt(k,n)
N = n
im = Image.new('L',(256, 256), 255)
draw = ImageDraw.Draw(im)
cx = 128
cy = 128
r = 96
draw.ellipse((cx - r,cy - r,cx + r,cy + r))
s = 2*pi/N
for i in range(N):
s1 = ((i*k)%N)*s -pi/2
x1 = r*cos(s1)+cx
y1 = r*sin(s1)+cy
x2 = cx + (cos(k*s))*(x1-cx) - (sin(k*s))*(y1-cy)
y2 = cy + (sin(k*s))*(x1-cx) + (cos(k*s))*(y1-cy)
draw.line((x1,y1,x2,y2))
/*! [python_zero]
released under the CC-BY license
https://github.com/kaityo256/python_zero
*/
続いて、大まかにではありますが、ソースコードを解説していきたいと思います。
関数gcd
まず、関数gcd(k,n)は最大公約数Greatest common divisorの略で最大公約数が1か否か、すなわち整数k,nが互いに素であるかを判別するコードです。
今回は特に、最大公約数の値自体を必要としてるわけではないため、従来のコードとは少し異なるかと思います。
ここでは、基本的に整数k,nがうまく設定されることを前提に話を進めさせて頂きます。
(⇨これまでの話からも分かるように初期値でk=0,1,n-1、またはk>nなどは不適)
上記の話を考慮すると、関数gcd(k,n)に値を投げるとelse文が適用されます。ここで何をしているかと言うと "ユークリッドの互除法"という手法を用いています。ユークリッドの互除法の詳しい説明は論点がずれるので省略させて頂きます。結論としては、ユークリッドの互除法を繰り返していき、片方の値が0になった時、整数k,nは互いに素でなく、片方の値が1になった時、整数k,nは互いに素となります。そこで、k,nが互いに素でない時にFalseを返しN芒星を描けなくし、k,nが互いに素であるときはコードが進むようにしました。
関数djt
次に関数djt(k,n)は互いに素Disjointの略で、最大公約数が1の時に、整数kを返す関数です。
そこで、上で定義した関数gcd(k,n)が値1を返した時、すなわち整数k,nが互いに素である時に初期値kを返すようにしています。
メインコード
for文までは画像の表示設定及び初期値設定なので詳細は触れません。
・s1=((i*k)%N)*s - pi/2
前項はただ、上記の『Nを法とする剰余項においてkを足すという巡回群』にN角形の中心をN等分したものをかけたものです。
分かりづらいのは、後項ですが、これは図を書く都合上、巡回群の'0'に当たる点を真上に持ってきたかったので、そのための調整だと思ってください。
・x2及びy2
x1,y1が円周上にのる事は自明ですが、この二点を基準に何やら複雑な操作をしているように思えるかもしれません。
ただ、これも'単純'な回転操作を行っているだけです。
高校数学の座標平面における回転を覚えているでしょうか?そんなのもう忘れたよって人は各自で「座標の回転」とでも調べてみてください。
悪魔の数字 '6'
N芒星は、N>=5で満たされると述べましたが、実は上のコードで書けないものが存在します。それがN=6のとき、すなわち6芒星です。
その理由が、このN芒星の描き方にあります。
今回のコードでは、円周上をN等分しその点をつないでいく事によってN芒星を描いています。
6芒星は6以下で6と互いに素である整数が1と5しかない事からも分かるように、円周上の点をつないでいくだけ、すなわち一筆書きでは描けません。
世にも奇妙に'6'という数字のみ書けないこのコード、少し深掘りしてみるのも面白いかもしれませんね!?
'あくまで' 円周上の点を結んで一筆書きが出来ないだけで、実は6芒星は一筆書き自体は出来ます!
もし、気になった人は各自で調べてみてください!
おわりに
このN芒星のコード、数学的背景もあり個人的に面白かったので記事として残すことにしました。
読みづらい、分かりづらい部分もあったかもしれませんが、自分なりには書きたいことは書けたので満足です😄