こんにちは、ロダンです。今回は解説記事ではなくちょっとした問題を、その背景とともに紹介したいと思います。
問題を述べるために必要な定義をしておきます。
マルコフ方程式と呼ばれる方程式
マルコフ方程式は対称式なのでその正整数解の各成分の位置は交換可能ですが、ここで定めたマルコフトリプルの場合はそうではないことに注意してください。第3成分が最大であるようなものを考えているので、
「マルコフトリプル」という名前は普通、単にマルコフ方程式の正整数解の意味で用いられますが、ここでは成分の大小関係に制限をつけたものをそう呼んでいます。また「マルコフ数」という名前も、普通はマルコフ方程式の正整数解に現れる数として定義しますが、少し制限がついています。ここで用いられる意味でのマルコフ数は、普通の意味で用いられるマルコフ数から
では本題。次の問題を考えます。
平方数であるようなマルコフ数は
まず
さて、ではそれ以外に平方数であるようなマルコフ数はあるのでしょうか?というのが問題1です。
この記事では、この問題に答えは出さず(というかそもそもまだわかってないので答えようがないです)、このような問題を考える背景について掘り下げます。数学という学問において、「重要な問題」とは、「なぜこのような問題を考えるのか?」という疑問に対してしっかりとした回答がある問題です。そのような問題は数学の新しい展開の鍵となるので、必然的に重要な位置を占めることになるのです。この記事によって、この問題を考える重要性を知っていただけますと幸いです。
唐突ですが、次のような概念を導入することにします。
この原始平方和トリプルはマルコフトリプルと同じく第1成分と第2成分を入れ替えることが可能で、第3成分は他と入れ替えられない特徴を持っています。さて、この平方和トリプルを他の概念から構成する方法を考えてみましょう。
まずは、マルコフトリプルから原始平方和トリプルを構成することを考えてみます。その上で重要となる定理を紹介します。
上記の命題は
上の記事
の命題7で証明しています(命題7では最大公約数を
マルコフトリプルから誘導される
具体例を見ておきましょう。
マルコフトリプル
次に、原始平方和トリプルを別のトリプルから構成する方法を考えてみます。平方和と聞いたときに、次の概念を思い浮かべた人も多いはずです。
いわゆる「直角三角形に現れる整数」のことですね。例えば、
普通「原始ピタゴラス数」は原始ピタゴラストリプルのことを指すことが多いですが、ここではマルコフ数の定義との類似性を見るために原始ピタゴラストリプルの中で最大であるもののみを原始ピタゴラス数と呼んでいます。
こちらのトリプルからも平方和トリプルを構成することができます。こちらはマルコフトリプルの場合と比べると非常に簡単で、原始ピタゴラストリプル
さて、ここまでで原始平方和トリプルの中にはマルコフトリプルから誘導されるものと原始ピタゴラストリプルから誘導されるものがありますという話をしてきました。すなわち、下の図のようになっているわけです。
原始平方和トリプルの内訳
ここで、赤の斜線で引いた2つのクラスの共通部分に入るものはなんでしょうか?というのが気になるところですね。これこそが、平方数であるようなマルコフ数が第3成分に来るような原始平方和トリプルなのです。次の命題が成立します。
平方数であるようなマルコフ数
つまり平方数であるようなマルコフ数を考えることは、図1の赤斜線の部分に入るような原始平方和トリプルを考えることと等価なのです!ちなみに、問題1から
仮に
さて、ここからはさらにこの2つの原始平方和トリプルのクラスについて掘り下げていきましょう。前の節で紹介したように、マルコフトリプルと原始ピタゴラストリプルは原始平方和トリプルを生み出す種なわけですが、両者ともに全列挙するツリーがあることが知られています。まずはマルコフトリプルの方から。
次のようなツリーを考える。
マルコフツリーはこんな感じのツリーになります。
このとき、次の定理が成り立ちます。
マルコフツリーにおける全ての頂点はマルコフトリプルである。また逆に、
この定理は本質的には この記事 の定理2で証明を与えています。
なお、
次に原始ピタゴラストリプルの方を見てみましょう。
次のようなツリーを考える。
こっちは3分岐のツリーです。原始ピタゴラスツリーはこんな感じのツリーになります。
このとき、次の定理が成り立ちます。
原始ピタゴラスツリーにおける全ての頂点は原始ピタゴラストリプルである。また逆に、
証明は 高校数学の美しい物語さんの記事 が詳しいです。
さてこれらのマルコフツリーと原始ピタゴラスツリー、2分岐と3分岐の違いはありますし世代ルールも全然違いますが、定理3と定理4はよく似ていますね。この2つのツリーの共通点を見出せれば大変面白いのですが、このままではあまりにもルールが違いすぎて比較が難しそうです。そこで、これらのツリーの頂点を、2つのトリプルの共通フォーマットである原始平方和トリプルに書き換えてしまいましょう。まずはマルコフツリーの方を書き換えてみることにします。各頂点のマルコフトリプルを、そこから誘導される原始平方和トリプルに置き換えます。
原始ピタゴラスツリーの方も同様に書き換えてみましょう。
どうですか?この2つのツリーになにか共通の構造のようなものが見えてきませんか?ちなみに僕はいまのところ全く見えません。ただ、目標として次のような問題が考えられます。
(1)ができたらそれだけでも結構面白いことだと思うし、(2)ができたらかなりの大事件だと思います。(2)は一見途方もない夢物語に思えますが、わずかな手がかりはあります。それは平方数かつマルコフ数である
実際はさらにいくつかつながっている辺があるかもしれませんが。どうですか?何か見えますかね?ちなみに僕にはまだ何も見えてません。こんなことを考えるなんて、荒唐無稽なのではないかとすら思えてきます。まあ、そう思ってなかったらこんなところに書かずに一人もしくは限られた複数人でこっそり研究してますからね。でも、何があるかわからないのです。もしかしたら、このツリーの世代ルールは実は簡潔に記述できるのかもしれないし、想定外の面白い事象が見つかるかもしれません。こういうときに
ということで、
さて、問題1のモチベーションもわかったところで現在の計算進捗についてお伝えしておきます。具体的にどんな計算を行なっているかというと、「調べる整数に一定の上限を設け、その上限を超えないような全てのマルコフ数について平方数かどうかを全て判定する」というようなことをプログラムを組んでやっています。否、プログラムをChatGPT(通称:チャッピー)に書いてもらってそれを動かしています。前まで使っていたものは平方数判定がうまく機能していないという指摘をp進大好きbot(
@non_archimedean
)さんから受けました。改良版も提供していただいたのでこちらを掲載いたします。ご提供ありがとうございました!Pythonで動きます。
import time
# 最大値制限の入力
max_value_limit = 10 ** int(input("max_value_limit: 10 ^ "))
B = (2**3)*3*5*7*11*13*17
# mod B で許可された値のリスト
allowed_mod_B = [0]*B
for i in range(B):allowed_mod_B[i*i%B] = 1
print(f"mod {B}平方数の比率: {sum(allowed_mod_B)/B}")
root = (1, 2, 5, 1%B, 2%B, 5%B)
# 平方数かどうかを判定する関数
def is_perfect_square(n):
l, r = 0, n+1
while l < r-1:
m = (l+r)>>1
if m * m > n:r =m
else:l = m
return l * l == n
# ツリー構築
def build_tree():
stack = [root] # スタックに初期ノードを追加
while stack:
a, b, c, a_mod, b_mod, c_mod = stack.pop()
# 最大値の制限をチェック
if c > max_value_limit:
continue
# 現在のノードの最大値を判定
mod_value = c_mod
#print(f"判定対象: {c}, mod {B}: {mod_value}")
# mod B フィルタを適用
if allowed_mod_B[mod_value] == 1:
#print(f"mod {B} フィルタ通過: {mod_value}")
if is_perfect_square(c):
print(f"平方数: (a, b, c) = {a,b,c}")
#else:
#print(f"平方数ではない: {c}")
#else:
#print(f"mod {B} フィルタでスキップ: {c}")
# 子ノードを生成
child1 = (b, c, 3 * b * c - a, b_mod, c_mod, (3 * b_mod * c_mod - a_mod) % B)
child2 = (a, c, 3 * a * c - b, a_mod, c_mod, (3 * a_mod * c_mod - b_mod) % B)
# 子ノードを追加(制限を満たす場合のみ)
if child1[2] <= max_value_limit: # 第3成分(c)をチェック
stack.append(child1)
if child2[2] <= max_value_limit: # 第3成分(c)をチェック
stack.append(child2)
# ベンチマーク開始
start_time = time.time()
# ツリーを構築
build_tree()
end_time = time.time()
# ベンチマーク結果を表示
print(f"ツリー構築の実行時間: {end_time - start_time:.6f} 秒")
私はプログラミングの素人なのでこのプログラムがいいものなのかどうかわからないし、そもそもこうやって公衆の面前でチャッピー産のプログラムを貼り付けることも正直恥ずかしいのですが、つよつよな人が改良してくれることを期待して晒そうと思います。
うまく機能しなかったコードは墓場(目次参照)に移しました。
何をやっているかはなんとなくわかる(というかそうじゃないとチャッピーに指示できないですよね)ので説明しておくと、最初に上限値を設定して、マルコフツリーを構成していきます。1つノードを追加するたびに、そのノードの第3成分が平方数かどうかを判定します。ただし、ノードを生成したあとでそのノードの第3成分が設定した上限値を超えていたらノードの追加をしません。このようにして、ノードを追加できなくなるまでノードの追加と第3成分の平方数判定を繰り返します。上限値以下の第3成分を持つ全てのノードを全て追加し終わったら、同時に上限値以下の全てのマルコフ数の平方数判定も終わっているという算段です。なお、第3成分の重複チェックは行なっていません。重複が起こらないとは限らないのですが、マルコフ予想という予想により、全てのノードの第3成分は重複しないだろうと言われています。
また判定する際に、毎回平方数かどうかを素直にチェックするのは時間がかかるので、
modフィルターは
また、ベンチマークの結果を最後に表示するようにしています。終わったことを知らせるサインであると同時に、どれくらいかかったかを知ることで次どれくらいの値で試そうかという指針が立つからです。あとは動作チェック用に平方数を判定する際にステータスを出力するオプションがついてます(今はコメントアウトしてあります)。
さて、このプログラムで
計算結果
プログラムが改修されたことで見つかったらどうしようとかちょっと思ってたのは秘密。
というわけで、今回はマルコフ数に関する1つの問題提起を行いました。この話初めて聞いた、どこから引っ張ってきたんだと思う人もいるかもしれませんが、これは私がここ数日で考えた話です。マルコフツリーと原始ピタゴラスツリーの間にある「なんかよく似てて関係ありそうだな〜」というような感覚を、数学的に考えやすいように整備しました。
一般にはあまり知られていないことだと思っていますが、解くべき問題にこうやってしっかりとした意味を与えたり、問題自体を考えやすいように体裁を整えることも数学者の大事な仕事です(少なくとも僕はそう思っています)。もちろんまだ問題自体は解かれてはいないわけですが、この作業は数学という学問を行う上で問題を解くことと同じくらい重要な作業であり、これによってその研究は半分くらい終わったと言ってもいいと思います。
ということでこの辺りで今回のお話は終わりにしたいと思います。この話の続きが気になる人は、自分で考察を進めてみてください。僕に許可を求める必要はありません、勝手にやってもらって大丈夫です。ではまた。
最初に記事に載せていた、平方数判定の関数が浮動小数点の誤差の関係で大きい(いうほど大きくない)数に対してうまく働かないコードを掲載して供養とします。現行のコードのように、二分探索で平方数かどうか判定するのが主流のようです。チャッピーは有能だけど、こちらもちゃんと知識を持っていないとうまく使いこなせないということの良い例ですね。でもこうやって恥を承知でコードを晒しておくとちゃんとした人がいろいろアドバイスをくれることがあるので掲載しててよかったなとも思っています。
import time
import networkx as nx
# ツリーを表す有向グラフを作成
G = nx.DiGraph()
# 最大値制限の入力
max_value_limit = int(input("max_value_limit: "))
# 初期親ノード
root = (1, 2, 5)
G.add_node(root, subset=0) # ルートノードのsubset_keyを設定
# mod 1008 で許可された値のリスト
allowed_mod_1008 = {
0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 112, 121, 144, 148, 169, 193, 196,
217, 225, 256, 288, 289, 324, 337, 340, 352, 361, 385, 400, 436, 441, 448,
457, 484, 505, 513, 529, 532, 553, 576, 585, 592, 625, 673, 676, 688, 697,
721, 729, 736, 756, 772, 784, 793, 820, 841, 865, 868, 889, 900, 928, 945, 961
}
# 平方数かどうかを判定する関数
def is_perfect_square(n):
if n < 0:
return False
sqrt_n = int(n**0.5)
return sqrt_n * sqrt_n == n
# ツリー構築
def build_tree():
stack = [(root, 0)] # スタックに初期ノードを追加
while stack:
parent, subset = stack.pop()
a, b, c = parent
# 最大値の制限をチェック
if c > max_value_limit:
continue
# 現在のノードの最大値を判定
mod_value = c % 1008
#print(f"判定対象: {c}, mod 1008: {mod_value}")
# mod 1008 フィルタを適用
if mod_value in allowed_mod_1008:
#print(f"mod 1008 フィルタ通過: {mod_value}")
if is_perfect_square(c):
print(f"平方数: {c}")
#else:
#print(f"平方数ではない: {c}")
#else:
#print(f"mod 1008 フィルタでスキップ: {c}")
# 子ノードを生成
child1 = (b, c, 3 * b * c - a)
child2 = (a, c, 3 * a * c - b)
# 子ノードを追加(制限を満たす場合のみ)
if child1[2] <= max_value_limit: # 第3成分(c)をチェック
G.add_edge(parent, child1)
G.add_node(child1, subset=subset + 1)
stack.append((child1, subset + 1))
if child2[2] <= max_value_limit: # 第3成分(c)をチェック
G.add_edge(parent, child2)
G.add_node(child2, subset=subset + 1)
stack.append((child2, subset + 1))
# ベンチマーク開始
start_time = time.time()
# ツリーを構築
build_tree()
end_time = time.time()
# ベンチマーク結果を表示
print(f"ツリー構築の実行時間: {end_time - start_time:.6f} 秒")
チャッピーというと、僕は朽木ルキアに入った義魂丸を真っ先に思い浮かべます。これを言いたいがためにこの記事を書いたのでした。終わり。