有理数体ℚに1の原始n乗根を加えたℚ(ζ)/ℚという拡大の自己同型写像群を、いくつかの巡回群の直積で表すことができるような、生成元の組み合わせを探す。ということです。
1の180乗根...の他の累巡回拡大を探すの最後に書いたように、プログラムを使って自動で探すということにチャレンジしました。
累乗の指数 n を入力して[実行]ボタンを押してください。ζn=1 であるようなζで、ℚ(ζ)/ℚ を決定します。
180なら3秒程度ですが、179なら5分を超えます(PCの性能にもよります)。数により計算時間は大きく変わります。
⇩以下に出力します ...
⇧
step3.生成元の組合せを探すプログラム(このページ下方)に、使用しているjavascriptプログラムの解説あります。step1.step2.はそのための準備です。
プログラムの都合で組合せは3つです。
■例1.■ 180を入れると、
同型写像の数:48 見つかった生成元の組合せの数:576 7 19 71 7 19 89 7 19 161 ...
と出ます。
7 19 71 は、σ7,σ19,σ71の組み合わせを意味します。σ7は σ7(ζ)=ζ7であるような写像です。ζはζ180=1である複素数で偏角が 360度/180 であるものです。
■例2.■ 5を入れると、
同型写像の数:4 見つかった生成元の組合せの数:2 1 1 2 1 1 3
となります。1は恒等写像ですから、一つの生成元で巡回してしまうことを意味します。1でない生成元が一つしかないものばかりであるということは、2つのグループに分けると巡回しないということでもあります。やはり、2はσ2で σ2(ζ)=ζ2であるような写像です。ただしここでのζはζ5=1である複素数で偏角が 360度/5 であるものです。
■例3.■ 39だと、
同型写像の数:24 見つかった生成元の組合せの数:48 1 2 14 1 2 38 1 5 17 ... 1 34 35 1 37 38 5 14 16 5 14 22 5 16 38 ...
となります。1が入っている組み合わせは、2つの巡回群、入っていないものは3つの巡回群にわけられるという意味です。
計算方法が固定体を作るかどうかを無視しているので、固定体を作らないけど掛け算すると全部を網羅する組み合わせというものが紛れているようです。
しかし、1段から3段までの巡回ということでは、もれなく出せているようです。
加える累乗根 | これまで考察て判明しているもの | プログラムでの検索結果 |
---|---|---|
1の5乗根による拡大 | <σ2>, <σ3> | 一致 |
1の15乗根による拡大 | 全体をひとつの巡回群で書くことはできない 累巡回は具体的に示していない |
8つの組み合わせが出る |
1の27乗根が作る拡大体 | <σ2>, <σ5>, <σ11>, <σ20> <σ23>, <σ3>が全体を巡回 | 全体を巡回ある6つは一致。その他に2つに分けた巡回も6つ出る |
1の16乗根が作る拡大体 | <σ5>と<σ7>, <σ5>と<σ13>, <σ13>と<σ7>, <σ13>と<σ15> の4組 | 4組の他にあと4組。固定体を持たないはずなので疑問が残る 3段階のものは出てこない |
1の180乗根が作る拡大体 | (<σ37>,<σ73>), (<σ41>,<σ101>,<σ29>,<σ149>), (<σ19>,<σ91>,<σ71>,<σ179>)から一つずつ取る2×4×4=32の組合わせは判明している | 3段階のの組み合わせが 576組出てくる |
さすがに576組も出てくると、「固定体を作る」というところを確認しなければならないのですが、その前にプログラムを紹介しておきます。
計算方法が違っていれば議論にならないので、紹介しておきます。
まず、素因数分解と、原始n乗根の洗い出しです。
以下に出力します ...
分解する数nを引数として受け取り、2から始まる素数で次々と割り切れるだけ割って、配列に付け加え、計算結果が1になったら終わりです。
/* 素因数分解をする * 引数: n 正の整数 * 戻値: 素因数の配列 */ function soinsuu(n){ var facs = []; var w=2; while(n>1){ while(n%w==0){ facs.push(w); n=Math.floor(n/w); } w++; } return facs; }
nは正の整数である必要がありますが、今回のいろいろな関数は同様な条件なので、nの値は別なところでチェックすることにして、ここでは正の整数と仮定して進めます。
・facsが素因数を入れていく配列で、[]は初期化です。他のプログラミング言語ではリストという呼び名で存在します。追加のできる変数などの要素の並びですが、配列のように要素にアクセスできます。
・wは割る数で、割り切れなくなったら次の素数に進むところですが、次の素数が難しいのでw++で次の整数としています。次の整数が素数でなければ、必ず割り切れなくなりますので、計算結果に影響しません。
・剰余算(余りを求める)はjavascriptでは%を使います。==0で余りがゼロ、つまり割り切れるということです。
・配列に要素を追加するのは.push(要素)です。
・Math.floorは整数部分を求める関数です。javascriptでは整数同士の割り算でも小数まで計算するので必要です。
n=6を例にします。ζ=cos(360˚/6)+ⅈsin(360˚/6) とすると(ⅈは虚数単位)、ζ,ζ2,ζ3,ζ4,ζ5,ζ6(=1) はすべて6乗すると1になります。このうち、6乗する途中で1にならず、6乗で始めて1になるものを、1の原始6乗根といいます。
1の原始6乗根は ζ,ζ5 の2つです。
genshikon(n)は、ζm (m=1,2,...,n-1)のうち原始n乗根となるものの指数を配列として返します。
計算方法は、nの素因数を求めて整数倍になるζの指数を排除して残ったものを原始n乗根と判断します。
/* 原始根を求める * 引数: n 正の整数 * 戻値: 原始根の指数の配列 */ function genshikon(n){ var facs = soinsuu(n); var tmp = new Array(n); //tmp.fill(0); for(var j=0;tmp.length>j;j++) tmp[j]=0; var lasts = 0; for(var j=0;facs.length>j;j++){ var s = facs[j]; if (s==lasts) continue; var i=1; while (tmp.length>s*i){ tmp[s*i]++; i++; } lasts = s; } var zs = []; for(var i=1;n>i;i++){ if (tmp[i]==0) zs.push(i); } return zs; }
・tmpは要素数nの配列で、0に初期化しておき、配列の要素番号(index)がnの因数となる時、0以外の数にして印とします。試したブラウザでfill(0)が使えなかったものがあってforで回しています。
・new Array(n)は[]と同様配列をつくりますが、引数が一つの時は要素数を表します。[n]ではnという値を持つ要素数1の配列になってしまいます。厳密には作られるものが「配列」と「Object」の違いがあるそうですが、この例の範囲では使用上同じです。
・lastsは前回の素因数を記憶しておき、同じであればスキップするためにあります。
・素因数とその整数倍についてtmpの要素の値を+1します。
・戻り値のために配列zsを作り、tmpの要素が0のままである要素番号を格納してゆきます。配列の要素番号は0からですが、forは1からになっていることでzsの要素の値は1から始まります。
拡大体の同型写像を、ζが変換される先がζiであるような変換をσiと表記する、つまり、
σi(ζ)=ζi (i=1,2,...,n)
としているので、この「原始n乗根となるものの指数の配列」は「同型写像であるσiの添字の配列」でもあります。
まずhtmlの記述です。
<p><input id="demoinput" type="number" min="1" step="1"> <button type="button" onclick="dodemo('soin')">素因数</button> <button type="button" onclick="dodemo('gens')">原始n乗根</button> <button type="button" onclick="dodemo('clear')">Clear</button></p> <p id="demochu"><i>以下に出力します ...</i></p> <p id="demooutp"></p>
inputのtype="number"はhtml5から使えるようになりました。未対応のブラウザでは単なるinputです。数値を入れるものですが、キーボードからは数字以外も入れられます。▲▼で増減できますが、その場合は min="1" step="1" が有効です。
buttonは3つ。onclickで引数を変えて同じものを呼ぶようにしました。
出力場所を2つ用意します。demochuは入力値が正の整数でなかったときなどの警告の出力、demooutは結果の出力です。
次にjavascriptです。
大雑把に言えば、過去の出力をクリアし、正の整数をinputから読み取り、引数のjobにより素因数分解と原始根を呼び出して結果を出力するというものです。jobが"clear"ならクリア後の作業をしないので結局クリアだけになります。
/* function dodemo(作業名) * プログラムの入り口です。引数の作業名は文字列 * soin:素因数、gens:原始n乗根、clear:表示のクリア * 依存:getint() 正の整数(文字列)またはnullを返す * 依存:cleardemooutp() 警告と結果の出力場所をクリアする * 依存:soinsuu 素因数を出力する。 * 依存:genshikon 原始n乗根を出力する。 */ function dodemo(job){ var poutchu = document.getElementById("demochu"); var poutp = document.getElementById("demooutp"); cleardemooutp(poutp,poutchu); if(job=="clear") return; var pinp = document.getElementById("demoinput"); var nn = getint(pinp,poutchu); if (1>nn) return; var anss; //nn = parseInt(nn); if(job=="soin"){ anss = soinsuu(nn); anss.unshift(nn+"の素因数分解:"); }else if(job=="gens"){ anss = genshikon(+nn); var ct=anss.length; anss.unshift("1の原始"+nn+"乗根の指数:"); anss.push("以上"+ct+"個"); }else{ anss=["??"]; } for (var s=0;anss.length>s;s++){ var ielm = document.createElement("i"); ielm.appendChild(document.createTextNode(anss[s]+" ")); poutp.appendChild(ielm); } }
getint()で得られる数字は基本的には文字列で、数値が1以上になっています。それ以外はnullです。javascriptではnullは0扱いになりますから、1>nnで2以上の数値(になる文字列)が得られます。soinsuu()は文字列を数値に変換して結果を出しますが、genshikon()は文字列だと都合が悪いので、引数を+nnとして数値に変更しています、
戻値はanssという配列に入れます。.unshift()は配列の前に要素を加えるメソッド。.push()は最後に要素を加えるメソッドです。javascriptの配列は要素が数値、文字列、などが混在してもOKです。
出力はi要素(<i></i>)にして出力するp要素に追加します。これは数値が文字列として繋がってしまわないようにとの配慮でしたが、ブラウザ画面からのコピーでは繋がってしまうので、スペースを連結しておくことにしました。
javascriptは変数の数値と文字列の区別が融通の高いものになっています。それを活かしてプログラムしています。htmlの要素から取り出したものは「文字列」です。
/* function getint(入力する場所,警告を出す場所) * input要素から文字列を取得して、数字だけの文字列として返します。 * 呼び出し側は、返り値が0より大きいことをチェックして先に進みます。 * 負数、小数点あり、数字以外が混在の場合は警告を発し、nullを返します。 * pinp,poutchuは[object HTMLParagraphElement] * pinp:入力する場所, poutchu:警告を出す場所です */ function getint(pinp,poutchu){ var nn = pinp.value; //var n = +nn; if(isNaN(nn)){ poutchu.appendChild(document.createTextNode("正の整数を入れてください。")); nn=null; }else if(0>=nn){ poutchu.appendChild(document.createTextNode("正の整数を入れてください。")); nn=null; }else if(nn%1!=0) { poutchu.appendChild(document.createTextNode("整数を入れてください。")); nn=null; } //poutchu.appendChild(document.createTextNode("ok")); return nn; }
要するにページのinput要素と警告出力用のp要素の位置を示すobjectを引数に、inputに入っている文字列を返す関数です。
正の整数でないときに、nullを返すことにしました。
まず、isNaN()で数値と解釈できない時にnullとします。
次の0>=nnはもちろん0以下のときです。これもnullにします。
次は、1で割った余りが0でない時で、小数以下の部分を持つものをnullにします。
それぞれ警告部分にメッセージを表示します。テキストノードを作ってapendしています。(p要素にテキストノードが連続して複数入るのはイレギュラーかもしれないのですが、やってみた範囲では問題はありませんでした。現在は既存のノードがi要素ですし、appendされるテキストノードは1つだけですので問題ありません)。
戻値は、+nnなどで文字列を数値にできますが、nullも返すので、そのままnnで返しています。
結果の出力場所と警告の出力場所のhtml要素のObjectを受け取って、中の子要素を全部削除した後、警告については最後に削除した要素を戻しています。警告の最後に削除する要素は最初から書いてあった「以下に出力します ...」の部分です。
/* function cleardemooutp(結果の出力場所,警告の出力場所) * 警告と結果の出力場所をクリアします。 * dodemo()から呼ばれて動作します。 */ function cleardemooutp(poutp,poutchu){ while (poutp.firstChild) { poutp.removeChild(poutp.firstChild); } var rm; while (poutchu.lastChild) { rm = poutchu.removeChild(poutchu.lastChild); } poutchu.appendChild(rm); }
戻値はありません。
以下に出力します ...
上で述べたことですが、
σi(ζ)=ζi (i=1,2,...,n)
としているので、この「原始n乗根となるものの指数の配列」から数値を一つ取って、「同型写像であるσiの添字」とみなし、複数回作用させると別の同型写像に移っていき、1に戻って巡回します。
この関数は、引数のsgenにsgenを掛けてはnで割った余りを計算し、1になるまでの数列を格納した配列を戻値として返します。
/* 巡回群を求める * 引数: n 正の整数 * 引数: sgen 生成元の添字 * 戻値: 生成される巡回群の元の添字の配列 */ function getCycs(sgen,n){ var cyc = [sgen]; var v = sgen; var ct = 1; while(v>1){ v=v*sgen%n; cyc.push(v); ct++; if (ct>=n) { cyc.push(-1); break; } } return cyc; }
・var cyc = [sgen]; はsgenを最初の値とする要素数1の配列を作るということです。
・v=v*sgen%n を計算して次々に配列に加えていきます。1になるまで繰り返します。sgenが同型写像の元でなかったときには何度計算しても1にならないことが起こります。無限ループに陥らないように、配列の要素数がnを超えたら最後の値を-1にして終了します。
呼び出し側では配列の最後の要素を確認して1でなければ巡回しなかったとわかります。
まずhtmlの記述です。
<p><input id="demo2input" type="number" min="1" step="1"> <button type="button" onclick="dodemo2('gens')">自己同型写像</button> <button type="button" onclick="dodemo2('clear')">Clear</button></p> <p id="demo2chu"><i>以下に出力します ...</i></p> <p id="demo2outp"></p> <p id="demo2cyc"></p>
「素因数分解と原始n乗根」の時とほぼ同じです。「原始n乗根」の算出結果を「自己同型写像」とみなして、選択してもらい、それを生成元とする巡回群を表示します。その表示場所が、id="demo2cyc" とされるp要素です。
次にjavascriptです。
dodemo()に比べて、"soin"がなくなって"gens"だけになっている所、自己同型写像の添字とみなしてi要素を作ったときにonclick属性をつけるというところが異なります。
/* function dodemo2(作業名) * プログラムの入り口です。引数の作業名は文字列 * gens:自己同型写像計算、clear:表示のクリア * 依存:getint() 正の整数(文字列)またはnullを返す * 依存:cleardemooutp() 警告と結果の出力場所をクリアする * 依存:cleardemooutchilds() 巡回群の出力場所をクリアする * 依存:dogetcycs() この関数を呼び出すイベントハンドラを記述する。 */ function dodemo2(job){ var poutchu = document.getElementById("demo2chu"); var poutp = document.getElementById("demo2outp"); var poutcycid = "demo2cyc"; var poutcyc = document.getElementById(poutcycid); cleardemooutp(poutp,poutchu); cleardemooutchilds(poutcyc); if(job=="clear") return; var pinp = document.getElementById("demo2input"); var nn = getint(pinp,poutchu); if (1>nn) return; var anss; if(job=="gens"){ anss = genshikon(+nn); var ct=anss.length; anss.unshift("自己同型写像σの添字:"); anss.push("以上"+ct+"個"); }else{ anss=["??"]; } for (var s=0;anss.length>s;s++){ var ielm = document.createElement("i"); ielm.appendChild(document.createTextNode(anss[s]+" ")); if(s!=0 && s!=anss.length-1){ ielm.setAttribute("onclick","dogetcycs('"+poutcycid+"',"+anss[s]+","+nn+")"); } poutp.appendChild(ielm); } }
イベントハンドラのonclickには dogetcycs() という関数の呼び出しを書きます。第一引数は 出力場所の要素につけられているid属性の文字列です。他の所で使用しているhtml要素を指すObjectは一旦文字列として書き出す関係で使えません。thisを書く手もありますが、要素の親子関係を守るのも、煩わしいことになってしまいます。.getElementById()メソッドの引数にするidの値を文字列を渡して dogetcycs() 関数の中で.getElementById()で場所を確定することにします。
いろいろなidをdodemo2()の冒頭でまとめて指定するので、poutcycidという変数に格納します。この変数の中の文字列を引数に書きますが、''で括らないと文字列が変数名として解釈されるので、忘れずに''します。他の2つの引数は数字なので''で括らなければ数値リテラルになります。ですから''は必要ありません。
dodemo2で仕掛けたイベントハンドラをからの呼び出しに応える部分です。第一引数からgetElementById()メソッドで場所を取得し、やはりi要素に入れて追加しています。
/* function dogetcycs(出力場所のidの値-文字列,生成元の添字,n) * function dodemo2() とは独立しています。 * dodemo2()で onclickをセットした要素をクリックすることで呼ばれる関数です。 * 依存:getCycs() 巡回群を求めます * 依存:cleardemooutchilds() 結果の出力場所をクリアします。 */ function dogetcycs(cpid,sgen,n){ var sgms = getCycs(sgen,n); //alert(sgms); var poutcyc = document.getElementById(cpid); cleardemooutchilds(poutcyc); var ct=sgms.length; sgms.unshift("選択した写像を生成元とする巡回群(σの添字のみ):"); sgms.push("位数"+ct); for (var s=0;sgms.length>s;s++){ var ielm = document.createElement("i"); ielm.appendChild(document.createTextNode(sgms[s]+" ")); poutcyc.appendChild(ielm); } }
生成元の出力場所と警告の出力場はすでにあるcleardemooutp()関数を流用できます。巡回群の出力場所をクリアします。手法は同じです。
/* function cleardemooutchilds(結果の出力場所) * 結果の出力場所をクリアします。 * dodemo2()とdogetcycs()から呼ばれて動作します。 */ function cleardemooutchilds(poutp){ while (poutp.firstChild) { poutp.removeChild(poutp.firstChild); } }
このページの冒頭巡回群の生成元の組合せを探すの計算のためのプログラム解説です。
dosss()関数はstep2.でやったように自己同型写像から得られる巡回群を3つ組み合わせてssschk()を呼び出すということを全部の組み合わせで繰り返します。
ssschk()は3つの巡回群から一つずつ取ってくる組み合わせをすべて計算して、元の自己同型写像が過不足なく出てくることをチェックします。戻値はTrue/Falseの論理値と2つの数値の配列です。Trueであれば過不足なく出てきたことを表します。
dosss()関数はTrueが帰ってきたら、その都度生成元の組を表示します。
最後にTrueであった数を挿入で一番最初に表示します。
ただし、当初期待したように見つかるたびに書いていくという動作にならず、プログラムの終了後全部がまとめて表示されます。
/* function dosss(作業名) * プログラムの入り口です。引数の作業名は文字列 * sss:巡回群の生成元の組合せを探す、clear:表示のクリア * 依存:getint() 正の整数(文字列)またはnullを返す * 依存:cleardemooutp() 警告と結果の出力場所をクリアする * 依存:genshikon 原始n乗根を出力する。 * 依存:getCycs() 巡回群を求めます * 依存:ssschk() 3つの巡回群の直積から全部の自己同型写像が過不足なく出てくることを確認する */ function dosss(job){ var ssschu = document.getElementById("ssschu"); var sssresult = document.getElementById("sssresult"); cleardemooutp(sssresult,ssschu); if(job=="clear") return; var pinp = document.getElementById("sssinput"); var n = getint(pinp,ssschu); if (1>n) return; var zs = genshikon(+n); var scct = 0; var izsct = document.createElement("i"); izsct.appendChild(document.createTextNode("同型写像の数:"+zs.length)); ssschu.appendChild(izsct); var sgmss = new Array(3); for(var i=0;zs.length>i;i++){ sgmss[0] = getCycs(zs[i],n); for(var j=i;zs.length>j;j++){ sgmss[1] = getCycs(zs[j],n); for(var k=j;zs.length>k;k++){ sgmss[2] = getCycs(zs[k],n); var success = ssschk(zs,sgmss,n); if(success[0]==true ){ //&& success[1]==0 scct++; var pelm = document.createElement("p"); for (var m=0;sgmss.length>m;m++){ var ielm = document.createElement("i"); ielm.appendChild(document.createTextNode(sgmss[m][0]+" ")); pelm.appendChild(ielm); } sssresult.appendChild(pelm); } } } } var pscct = document.createElement("p"); pscct.appendChild(document.createTextNode("見つかった生成元の組合せの数:"+scct)); sssresult.insertBefore(pscct,sssresult.firstChild); }
dosss()では3つの生成元のすべての組み合わせが必要で、並び方は問われませんから、forの動く範囲を変えています。
次は、dosss()関数から呼ばれるssschk()です。
zs,cpzsは自己同型写像全体の配列です。3つの巡回群の配列の要素の積で出てきた写像(の添字)を自己同型写像全体の配列から削除していきます。同じ写像が出てくると配列内に見つからなくなりますから即falseを返してreturnします。ここで配列の要素を減らすことをしますから、zsをそのまま使ってはなりません。引数が配列やObjectの場合は参照渡しになっていますから。そこでコピーを作ります。それがcpzsです。
.slice()メソッドで同じ構造の配列を新規に作り、各要素の値をコピーします。いわゆるシャローコピーになりますが、zsの配列の要素は数値なので気にする必要はありません。Firefoxをはじめ普通のブラウザでうまく行っています。
ところがあるブラウザ(midori)では不安定であるはずの組み合わせが見つからないと報告されます。結局genshikon()関数を新たに呼んで取得することにしました。これならzsを引数に入れる必要はなかったというところですが、そのままにしています(まだ役割はある)。
このブラウザはfill(0)メソッドも使えなかったなどあまり信頼が置けないのですが、プログラムでコメントアウトしている//3の方法や//4の方法を使いましたがこれでもだめでした。変数のスコープなどで干渉を起こしていないか、かなり調べたのですが今の所原因不明です。nとして、2,3,4,...と順番に実行していくと8でかなりま確率で組合せの数が0になります。genshikon()関数を使えば大丈夫です。
/* function ssschk(自己同型写像の配列,3つの巡回群の配列の配列,n) * 3つの巡回群の直積から全部の自己同型写像が過不足なく出てくることを確認する * dosssからの呼び出しで作動するように設計。 */ function ssschk(zs,sgmss,n){ //1 var cpzs = zs.slice(); var cpzs = genshikon(+n); //3 var cpzs = new Array(zs.length); //3 for(var i=0;zs.length>i;i++) cpzs[i]=zs[i]; //4 var cpzs = []; //4 for(var i=0;zs.length>i;i++) cpzs.push(zs[i]); var ct = 0; var ptr = 200; var retv = true; loop3: for(var i=0;sgmss[0].length>i;i++){ for(var j=0;sgmss[1].length>j;j++){ for(var k=0;sgmss[2].length>k;k++){ var sss = sgmss[0][i]*sgmss[1][j]*sgmss[2][k]%n; ptr = cpzs.indexOf(sss); if (ptr==-1){ retv = false; break loop3; }else{ var removedItem = cpzs.splice(ptr, 1); alert(zs.length,cpzs.length); ct++; } } } } if(cpzs.length>0) retv=false; return [retv,zs.length-cpzs.length,ct]; }
ⅈsin(360˚/6) と使った虚数単位ⅈです。
単にisinθでは虚数単位だとわからないのでisinθなどとしてきました。<i>で斜体にしています。
unicode文字の中に適当なものがないか探してみました。
まず、BMP内では
ℹ U+2139 INFORMATION SOURCE
ⅈ U+2148 DOUBLE-STRUCK ITALIC SMALL I (sometimes used for the imaginary unit)
ⅉ U+2149 ⅉ DOUBLE-STRUCK ITALIC SMALL J (sometimes used for the imaginary unit)
BMP外には、Mathmatical Bold Small I などMathmaticalがついたものが若干あります。でも虚数単位とは書いていない。
𝓲 U+1d4f2
𝒾 U+1d4be
𝐢 U+1d422
𝒊 U+1d48a 𝒊sinθ
普通のiにして、書体を指定してみると次のようになります。これはPCの環境により見え方が異なります。
isinθ
isinθ
unicodeでも、PCによりフォントがそこまで文字が入っていないという可能性があります。
角度を表すdegreeに使いました。U+00B0の方は全角扱い(日本語-日本のロケールのせいだと思いますが)になるので、U+02DAを使っています
˚ U+02DA RING ABOVE
° U+00B0 DEGREE SIGN