Windows 11をダウングレードしてWindows 10に戻す

UNDOでエラーが出ている.UNDOBASE::CommandStartで (!UndoCurptr->topUndo)のエラーが出ている.このエラーはリリースモードでは発生しない.⇒_ASSERT_NEVERはデバッグ専用のアサーションだ.⇒UNDOBASE::MakeNewCommandでは出口で明示的にtopUndoにNULLを代入している.その後実行されるBackupPointDataでバックアップが実行された場合にのみ値が入る.UPDATETBLでは更新されるカードデータがバックアップされるが,インポートの場合にはオブジェクトが不在の場合があり,このようなときにはtopUndoは空のままになる.⇒オブジェクトが空の場合は停止しないようにした.

VS2017の開発環境でゼルコバの木を起動してほとんど止まってしまう.リリースモードでは動いていたのだが,どこか壊してしまったのかと思って少し古いパッケージに戻って試してみたところ,リリースモードでも立ち上がらなくなってしまった.リリースモードでは起動時に表示しているスプラッシュウィンドウのところでフリーズしてしまう.デバッグモードの場合はほとんどどこでハングしているかわからない状態だ.ブレークを掛けてみるとほとんどつねにコンストラクタのマクロの中で止まっている.昨日まではほとんど問題なく動作していたように思われるのだが…まったく心当たりがないので,とりあえずWindows 11をダウングレードしてWindows 10に戻してみた.

ダウングレードはWindows 11にアップグレードしてから10日以内に行わないと手遅れになる.アプリケーションの再インストールなどは必要ない.VS2017を起動して,ゼルコバの木を実行してみる.⇒何事もなかったかのようにあっさり立ち上がってきた.リリースモードでもデバッグモードでもまったく問題なく走っている.Windows 11では何かひどいデグレード(劣化)が内部で起きているように思われる.リフォームの志向する方向(ポリシー)はそれほど悪くないので,残念だ.

バックアップから元の仕掛り版に戻して実行しようとしたところ,障害が再発してしまった.プログラムのコードに何か致命的な不良があるのだろうか?⇒リリース版は立ち上がってくる.デバッグモードではコマンドライン引数で起動するようになっているので,リリースモードと同じノーマルモードで起動するようにしてみた.⇒これなら,リリースモードと同等に実行できる.ADLのインポート処理に何か問題が起きているのだろうか?⇒UNDOを復活させたのが過負荷になっているのではないか?⇒どうも,そういうことのようだ.ということは,Windows 11でも動作していたということだろうか?

確かに,それは言えるかもしれないが,「遅くなっている」というのは事実なのではないか?単純なファイルコピーでもWindows 10より遅くなったという印象がある.そのため,重いサンプルを読み込むときには,それが隠しきれなくなるのではないか?少なくともリリースモードでは(UNDOが入っていたとしても)開けないとおかしい.⇒リリースモードでチェックしているとき,スプラッシュウィンドウが開いたままになっていたという事象があったが,開発環境ではスプラッシュウィンドウは表示されないのではなかったろうか?⇒確かに,それはおかしいね.何か勘違いしていたかな?しかし,通常,スプラッシュウィンドウは2, 3秒で閉じることになっているのに,開きっぱなしというのはやはり動作不良と見るしかない.

いずれにしても,動作するようになったので作業を再開できる.UNDOが入るとほとんど止まってしまうという問題は別途考えることにしよう.というか,インポートはUNDOの対象外なのではないか?ファイルのオープンではUNDOのバックアップを取っていない.原理的にはそれと同じはずだ.インポートは一覧表のレコードの更新機能を使って実現している.レコード更新はUNDOの対象なので,それに合わせているが,これは廃止すべきではないか?⇒それが妥当だと思う.インポートでUNDOしないことにすれば,UNDOを復活させても支障ないはずだ.

インポートはSendUpdateDataという関数を反復呼び出すことで実現している.この関数ではインポートから呼び出されているのか,そうでないのかを判別できない.インポートでは処理を開始する前に,Z.mImportStartという通知を送り,Z.mImportEndで閉じるようになっている.これで状態を判別できるのではないか?LINKTABLE::ImportStartでは,ImportFileNameにファイル名をコピーしているだけで,フラグは立てていない.⇒ImportFlagというフラグを作っておこう.⇒これでようやく,懸案の「難問」に戻れる.

呪われたサンプルTC3000(4720点)を1353点まで圧縮した「極小サンプルファイル」を作ってあったはずだが,見つからない.残っているのはCT 3000-1906.ZELまでだ.「GOO3000-1303.ZEL」というのはあるが,これは反例ではない.間違えてゴミ箱に入れてしまった可能性はあるが,空にしてしまったので取り戻すことはできない.「BUG3000-1906.ZEL」というファイルもある.「CT 3000-1906.ZEL」とどう違うのだろう?「53直系血族図.ZEL」というのもある.1931点で最小という訳ではないが,最後に扱っていたサンプルだ.⇒これを継承したのが「CT 3000-1906.ZEL」で「BUG3000-1906.ZEL」はそれと同じだ.「BUG3000-1906.ZEL」は下図のようなファイルで,先祖ノードの「35」は画面の下の方に隠れている.

image

明らかにまだ,かなり削れる余地は残っている.この際なので,完全に「極小」な反例サンプル,つまり,どの1点を取り除いても解けてしまうようなサンプルを追求してみることにしよう.UNDOが使えるようになっているので,それほど厄介な作業ではない.ノードを削って,解けてしまった場合にはUNDOで戻せばよいだけだ.先祖ノードの「35」には{ 1493, 23, 373 }の子ども3人がいる.多分2系統残せば十分と思われる.前回は「23」と「373」の2人を残している.最大系統は「23」だ.「1493」は6点しかない.

image

「373」はもう少し大きく,49点ある.

image

まず,この系統を全削除してみよう.やはり,この系統を外すと再現できなくなってしまうようだ.とりあえず,「1493」の直系血族図を取って全削除.⇒失敗した.親族図に切り替えてからソートすればよかった.親族図なら短時間に描画完了できるが,全体図ではループカウントオーバーするまで待たなくてはならない.⇒おかしい.先祖ノードの「35」の子どもがいなくなってしまった.

▲BUG3000-1906.ZELで1493の系統を全削除して,先祖ノード35とその子どもの23, 373のリンクが切れてしまった.⇒全削除ではなく,1個づつの削除でも同じだ.

全削除のバグと思われるが,「35」に「23」と「373」を接続して先に進むことにしよう.⇒かなり,まずい.35→{23,373}の接続ができない.登録ボタンを押しても子ども欄が空欄になってしまう.

どうも,この辺りは全面的な見直しが必要なようだ.UNDOで初期状態まで戻れるだろうか?ちょっと心もとない気がするが…⇒ダメなようだ.もう一度読み直そう.

▲氏名並び替えを実行しただけで,系統並び替えが発動されている.⇒カード並び替えはUNDOの対象になっている.UNDOではつねに系統並び替えを実施するようになっていたのではないか?

▲35の子ども欄に23と373を入力→登録で名前が消えてしまう.23の父欄に35を入力→登録でも名前が消えてしまう.つまり,親子関係の登録が完全に壊れている.⇒UNDOで復元はできた.⇒全体図上で全削除を実行したら,正しく動作した.カードの登録・削除の動作が開いている画面のモードに影響されるなどということがあるのだろうか?

終端ノードは事象に関わりがない可能性があるので,一括除去してみよう.3倍数は女子として登録されているので,まとめて削除できる.438点ある.削除はできたが,解けてしまった.昨日の手順で裾刈りをしてみよう.昨日のメモに「1335」を含むとあるので,それより下の世代をカットしてみる.⇒1473点まで削除したので,一旦保存しておこう.BUG3000-1473.ZELとしておく.⇒少し欲張って,あと2世代裾刈りしてみよう.いや,ダメだ.だめということは昨日確認されている.

「23」は5人子どもをもっている.{15, 245, 3925, 61, 981}だ.「373」の子どもは2人で{1989, 497}だ.これらのいずれかの子どもの系統を切断できるかどうかやってみよう.まず,「373」の子どもから見てみる.2人の子どものうち,1人は女子なので,おそらく削れるだろう.もう一人の子どもは残すしかないことは間違いない.「23」の子どものうち,2人(15, 981)は女子なので,まずこれからカットしてみる.⇒OKのようだ.残り3人を試してみる.まず,「245」⇒loopcnt=25で解けてしまった.⇒他の2つも同じだ.この手順を自動化することは不可能ではない.たとえば,以下が考えられる.

  1. 対象グラフをTとする
  2. TのすべてのノードNについて,以下を行う
  3. ノードNをTから削除して,検定がカウントオーバーするか否かを検査
  4. カウントオーバー→Nの下流系を除去したTの部分グラフをT’とする
  5. 検査に合格したT’のうちの最小のものを選んで,Tとする
  6. これをどの1点を除去してもカウントオーバーしなくなるまで繰り返す

このアルゴリズムによって,最適ないし最小であるか否かは別として,「極小な反例サンプル」が得られることは間違いないと考えられる.問題は計算コストだが,実効的には多項式時間で可能なのではないかと思われる.ステップ4で1点除去するたびにグラフは確実に小さくなってゆくからだ.前回は1353点まで圧縮したので,まだまだ削れることは確実だが,手操作で「極小な反例サンプル」を獲得するのはかなり難しい.「極小な反例サンプルを自動生成」するプログラムを書くのはそれほど難しくないとは思われるが,デバッグのコストも考慮しなくてはならないので,ここでは保留して,もう少し考えてみることにする.

▲BUG3000-1471.ZELに孤立カード「1989」が残っている.⇒どこかで手順に手抜かりがあったのだろう.※このカードを削除して,BUG3000-1470.ZELとして保存した.

Windows 10にダウングレードしてから,VS2017を管理者権限で起動できなくなった.2022/04/14で「Windows 11にアップグレードした」ときと同じ事象だ.ただし,今度は①タスクバー上のアイコンを右クリック→Visual Studio 2017を右クリック→管理者として起動では起動できる.②Visual Studio 2017のショートカットのプロパティ→詳細設定→管理者として実行はオンになっている.前回は,①では起動できなかったが,②を設定して起動できるようになった.今回はその逆で,②では起動できないのに,①では起動できる.タスクマネージャが開いていると起動できるという事象は共通だ.⇒とりあえず,①の方法で起動できることがわかったので,このまま進むことにする.

あれこれやってきたが,呪われたCT3000反例の難問を解くためには,どうしても「極小な反例サンプル」を作るしかないという雰囲気に傾いている.原理的にはそれほど難しくはないが…実際に構築する際の難易度を考えてみよう.外部アプリとして完全に外に出すというのも少し無理がある.現在コラッツツールから直接ゼルコバの木を起動→インポートはできているが,極小反例サンプルを作るためには,少なくとも,①任意のカードNを指定して削除,②系統並び替えの実行,③カウントオーバーしたら処理を中断して結果を返す,④Nの部分木を削除,⑤残りの部分木をファイルに出力…などのことができなくてはならない.

これらのことをすべて外部でやると言うのは現実的ではないので,DLLに作り付けで組み込むしかない.DLL上であれば,ゼルコバの木データのすべてのデータにアクセスして,任意の操作を実行することが可能だから,できない話ではないだろう.VBに組み込むということはあり得るだろうか?現在手操作で行っている反例サンプルの圧縮はすべてゼルコバの木というVBアプリ上でやっているのだから,原理的には実装可能なのではないか?どういうストーリーになるか,書いてみよう.

  1. VBアプリに以下のような処理Pを組み込む
  2. 反例サンプルファイルFをロードする
  3. カードテーブル上のカード数をNとし,反例カウントを0とする
  4. カードテーブルのすべてのレコードRについて以下を実施する
  5. カードRを削除する
  6. 系統並び替えでループカウントオーバーが発生したという結果を何らかの方法で受理して,事象Aとする
  7. 事象Aが発生していないときは,UNDOを実行して,ステップ2に戻る
  8. 事象Aが発生したとき
  9. (1)反例カウントをインクリメントする
  10. (2)Rを基準ノードとして系統並び替えを実施する
  11. (3)Rの直系血族を選択して全削除する
  12. (4)残ったカード数Mをカウントして(R, M)の対をテーブルTに記録
  13. (5)UNDOを実行して,ステップ2に戻る
  14. すべてのレコードを処理するまでステップ4から繰り返す
  15. テーブルTの記録をスキャンして最小のCを与えるRを決定する
  16. カードRを対象にステップ5~11までを実行する
  17. 反例カウント>0ならばステップ3に戻る
  18. ファイルFは極小な反例サンプルである:処理P完了

基本的にステップ6を除けば,すべての手順は現行コードを使って実装可能と考えられる.「カード削除」の戻り値で「ループカウントオーバー」を返すという処理は現行には存在しないが,不可能ではない.現行では「カード削除」の戻り値は次の(主選択)カードの参照番号になっているが,エラーが発生した場合には負値を返すようになっているはずだから,何か適当なエラーコードを決めて,それを返すようにすればよい.ただし,エラー発生の地点から「カード削除」処理の出口までそのエラーコードを受け渡しするのは難しいかもしれないが,グローバル変数に入れておいて,「カード削除」の出口でチェックすればよい.

この処理全体をDLLに組み込むことは難しくないし,その方が効率的でもあるが,あえてVBに独立の処理として組み込む方がベターだと思う.そうすれば,DLL側の既存コードはまったくいじらなくて済むので,既存コードのデバッグとツールのデバッグを完全に切り離すことができる.これが実装できるとかなりおもしろいことになる.「極小な反例サンプル」はいろいろな場面で必要になるが,これまでは手操作でしこしこと作ってきたところが完全に自動化できる可能性がある.「障害」の種別はさまざまだが,ある特定の「障害」が発生しているとき,そのエラーコードを受け渡すだけで,その障害を再現する「極小反例サンプル」が作れるとしたら,これはかなりすごいことではないかと思う.これはやってもやらなくてもよいことではなくて,「MUST」だと思う.

実装に移ることにしよう.VBにはすでにこの種のテスト用ツールのセットが「包括自動テスト」として確立している.たとえばその中には,全体図テスト,親族図テスト,完全木テスト,パックマン全点/単点テスト,ハーレム全点/単点テスト,ランダムインポートテスト,ファイルオープンテストなどがある.「極小反例サンプル」生成ツールはテストツールではないが,ある意味でそれらよりも強力なツールになるだろう.VBアプリは通常の使い方もできなくてはならないので,やはり最初からメニューで起動するようにしておく必要がある.

エラーコードとしては,ERR_COMPLETETRIBEというのがあるので,これを使うことにしよう.TRIBEBOX::CompleteTribeBoxはブール値を返す関数なので,戻り値には代入できない.ここで例外をスローしたらどうなるのか動作を見てみよう.これまで見た限りではノーマルに描画できるときにはループカウントが50を超えることはなかったと思われるので,REDLINEも50にしておこう.⇒StackTribeGeneのトラップでエラーコードがERR_STACKTRIBEGENEに切り替わっている.また,TOPOLOGY::SetPhaseでフェーズ遷移エラーが起きる.⇒例外をスローすると,「検定に失敗しました」でアプリ終了してしまう.やはり,グローバル変数で渡すしかなさそうだ.⇒fatalerrorという変数があるので,流用してみよう.(わたしはミニマリストなのでできるだけ変数の数は増やしたくない…)

UNDOBASE::CommandStartでエラーコードをリセットし,CommandEndで渡すようにしようとしたが,UNDOBASE::CommandEndではまだ系統並び替えが実行されていない.それを行っているのはUNDOSYSTEM::CommandEndだ.fatalerrorではダメなようだ.どこかでリセットしているのだろう.KAKEIZU::closeFamilyBaseでゼロを代入している.ERR_COMPLETETRIBE=-3051だ.⇒mZelkova::mDeleteCardで戻り値を書き換えている.⇒いや,違う.FAMILYTREE::DeleteCardDataはCommandEndの戻り値を無視している.⇒いや,ほとんどのコマンドがそうだ.⇒Z.mDeleteCardまではエラーコードが通った.

カード削除を実行しているVBのDeleteFuncでは,Z.mDeleteCardの戻り値をCurrefnumに格納し,Z.DeleteCountを返している.極小反例ツールではZ.DeleteCountを直接呼び出す作りになると思われるので,ここまででよいことにしよう.次に,メニューコマンドを整備しておこう.包括自動テストの下に,「極小反例サンプル生成」というコマンドを新設しておく.この実行メソッドはOpenFileTest.vbにおくことにする.メソッド名はBuildMinimalCounterSampleとし,現在開いているファイルを対象とすることにする.

VBが持っているカードテーブルは実際には一覧表のことだが,カードを削除したり,それをUNDOで復元したりしたとき,カードの並びが保存されているかどうか?ということは当てにできないので,別にカード番号だけを集めた配列を作った方がよい.これを2次元配列として残留カード数を格納できるようにしておけばよいのではないか?

ステップ11で「Rの直系血族を選択して全削除する」としているが,Rの直系血族に属するカード数をカウントするだけでよい.残留カードが最小となるのは,直系血族最大の場合だからだ.ただし,直系血族のすべてのカードではなく,Rの下流系のみをカウントしなくてはならない.つまり,Rの親との関係を切断した上でその直系血族をカウントするようになる.ちょっと厄介だ.親子関係を切断するためにはカードRの個人情報を読み出した上,カード登録操作を行わなくてはならない.(VB上には直接親子関係を切断するようなコマンドはない)

カードRの下流系に含まれるカード数をカウントする手続き:

  1. カードRのカード情報を取得する
  2. Rのカードの父母欄を空欄にして登録する
  3. 図面種別:親族図,親族範囲:直系血族に設定する
  4. Rを基準ノードとして系統並び替えを実行する
  5. 一覧表:表示範囲:系図画面上のカードを一覧表にロードする
  6. 一覧表のレコード数を取得する

これを実装する前に前段の動作を確認してみる.つまり,カード単点を削除してカウントオーバーが,最初のループでどの程度発生するかを見てみることにする.どうも,この方法は非現実的なものであるような気がする.1400点余りの反例サンプルを一度描画するだけで少なくとも1分近くは掛かっている.検定が一周するのに1400回系統並び替えを実行するとすると,それだけで1400分,24時間掛かってしまう.実際には1点を処理するのに3回程度系統並び替えをやり直すので,おそらく,2日ないしそれ以上掛かることになりそうだ.最後の極小解を得るまでにそれを何段繰り返せばよいのか分からないが,少なくとも2, 300日,おそらく1年以上掛かってしまうのではないかという気がする.

必ずしも最適解が得られるとは限らないが,もう少し効率的なアルゴリズムも考えられる.カード単点を削除して,それが反例だと判定された時点でそのカードセットを開始点に切り替えればよい.現行でも,すでに「3」と「22」は反例を生成するという結果が出ているので,それを使えばもっと急速に収束するような動きになるはずだ.「3」ないし「22」が削除できるということを手操作で確認してみよう.⇒1400点もあって,「3」を目視で見つけることができない.メニューバーの検索ボックスはあいまい検索なので,同名カードが663件も出てくる.「検索パネル」では「姓名の完全一致」検索ができるが,悲しいことに,「検索条件にマッチングするカードはありません」.

どうにもならないので,氏名でソートしたものを保存しておこう.mmm…確かに「3」というカードは載っていない.「22」も同様だ.⇒間違えていた.現在行っているテストでは,参照番号ではなく,「氏名」のうちの「姓」を読まなくてはならなかった.作り直しが必要だ.

UNDOSYSTEM::CommandEndで,UNDOBASE::CommandEndを呼び出した後,UndoCurptrが空で停止した.これまでこのようなエラーは出たことがない.⇒修正を誤っている.確かに「姓」には「ノード番号」が入っているが,カード操作では「参照番号」を使わなくてはならない.つまり,「3」ないし「22」のカードではなく,#3ないし,#22のカードでなくてはならない.

#3は「8195」,#22は「8213」,いずれも単身の子どもを1人持つカードだ.確かに,このようなカードは削除できる.これを進めれば,高々Nステップで極小解に達することができるだろう.これなら丸一日で完了する可能性はある.しかし,子孫系をカットする手順が問題だ.要は始系列に属さないノードセットを構成すればよいのだが… この操作をDLL側でやっているのなら方法はいくらでもあるのだが…

VBには先祖並び替えというのがあるから,先祖リストを取ることはできる.先祖リストの先頭を除いて,それに続くノードを対象に,直系血族図を作ってそれを全削除でよいのではないだろうか?この方法の利点は,全体木の先祖ノードを削除して反例になる場合にも対処できるという点だ.(通常はこういうことは起こらないが…)

先祖リストの取得関数 mGetSortList を呼び出すのに,引数に参照番号を入れて渡しているが,これは何に使っているのだろう?⇒この関数は先祖リストだけでなく,子ども並び替えや配偶者並び替えなどにも使う汎用関数だ.先祖リスト取得の場合にはこの値は参照されていない.リストはSortListに格納され,バックアップを指定すると,InitialListにも同じ値が保全される.

どうもUNDOが壊れてしまっているようだ.初回2枚のカードを削除したあと,新たに開始したテーブルで12番目ノードを処理中にUNDOで無限ループしている.まとめて大量削除したものをUNDOしているとすればそのようなことも起こり得るが…いや,これはUNDO動作ではなく,UNDOBASE::CommandEndの中で「今回保全したノードの値が更新されているときには今回リストに追記する」ということをやっているところだ.何が起きているのかさっぱり見当も付かない…この処理はUNDOに全面的に依存しているので,UNDOが動かないことには始まらない.UNDOをリセットするというコマンドはあったろうか?AutoMergeTestではresetUndoChainを実行しているが,VBから直接リセットするコードは存在しない.AutoMergeTestでは今回と同様UNDOを使って処理を組み立てている.

UNDONODEやUNDOCOMMANDは,noduleから直接派生したプリミティブなオブジェクトでLISTなどのような要素管理の仕組みは持っていない.DumpUndoChainという関数がある.これであるUNDOコマンドの下にあるチェーンをダンプできる.この関数はUndoProcessから呼び出されている.UNDOBASE::CommandEndにも仕掛けてあった.⇒なぜだろう?確かにむちゃくちゃ長いチェーンが生成されている.コマンドはDELALLCARDだが,完全に無限ループしているように思われる.チェーンには主にCARDLINKとMARGLINKのコピーが保全されているが,カード数は1500より少ないのだから,すべてのカードを削除したとしても,3000を超えるはずはないはずなのに,30000を超えている.一つのカードを削除するとその周辺もバックアップされるから,この数倍,いや,10倍とすれば30000というのは不思議な数ではない.

単点を削除したときのバックアップが14点くらいあるので,確かにそのくらいにはなるかもしれない….当然その大半は重複とみられる.どうすればよいか?保全の対象ノードに何か目印を付けるしかないのではないか?しかし,UNDOで保全されていると言っても,時間経過によって変化するから,そのときの「現時点」においてすでにバックアップされているか否かを判定できるようになっていなくてはならない.従って,フラグのようなものではなく,何か計数的なものが必要だ.UNDO開始のときにNOを決定し,開始から終了までの間に保全されたオブジェクトにはそのマークNOを書き込んでおくというのはどうか?

これはシステムに1個だから,UNDOBASEないし,UNDOSYSTEMの静的変数とすべきだろう.グローバル変数でもよいかもしれない.UndoCounterというファイル内の静的変数を定義してみた.初期化はUNDOリセットでよいだろう.いや,現行でもUNDOBASE:etUndoListでは,「Undoリスト上にobjectの複製がすでに存在する場合はFalseを返す」ということになっているのだが…SetUndoListには正・負・ゼロの3モードがあり,正がオブジェクトの生成,負がオブジェクトの削除,ゼロがオブジェクトの更新に割り当てられている.SetUndoListでは更新の場合以外は,無条件ですべてのオブジェクトを保全している.これは見落としを避けるためという理由ではないかと考えられるが,一つのコマンドチェーン上に同じオブジェクトが複数存在する意味はないと考えられるので,すべて弾くということにしてみたい.

「部分図オブジェクトは更新する@20180207」というコメントもある.これはSetUndoListの第3引数がUNDO_COPYの場合には上書き更新するというものだ.よくわからないが,ちょっと無視しておこう.

▲上記エラーが再発した.UNDOSYSTEM::CommandEndで,UndoCurptrが空で停止.

確かに,事後にオブジェクトを更新するということはやっている.なぜこういうことが必要なのか調べる必要がある.UndoCurptr->topUndoが空のときには,PergeZenpoChainで「前方コマンドチェーンをカット」している.このケースでは初回のUNDO処理であり,かつtopUndoが空であるため,UndoCurptrまで削除されているものと思われる.つまり,このような状態はあり得ると見るべきではないか?

▲BaseLink空により,FAMILYTREE::RestoreBaseCardで停止した.その後,バックアップタイマーでファイルをバックアップ中(!topology->SenzoOffList->count)で停止した.

 

TC3000という呪われたサンプル

TC3000という呪われたサンプルの呪縛をなんとかして解かなくてはならない.TC3000の内容をまるごと包含しているTC4000などのもっと大きいサンプルは容易に描画できているのに,このサンプルだけは収束しないという理由を是が非でも突き止めなくてはならない.さもなければこのソフトは永遠に陽の目を見ることはないだろう.いや,むしろ,このようなサンプルを与えられたことは法外な幸運であったと考えるべきかも知れない.⇒多少の進展はあった.

TC3000は4720点のカードを含むゼルコバの木的には巨大系図だ.三極検定では系図図面上のすべての描画要素の座標を調整して「最適配置」を実現しようとする処理だが,下流での調整は上流にも影響を与え,上流での調整は下流にも影響する.さらに,「計算上の誤差」という問題があるため,完全な厳密値を用いると収束できない場合がある.この問題は系図木の規模が大きくなればそれだけ累積的なものになる.

TC3000サンプルの出力するADLファイルをテキストエディタで直接編集して,5461という終端ノードを除去してやれば,有限時間内(loopcnt=67)に描画することができる.テスト回数3000という設定ではその2倍の5999までの奇数がテストされるから,5461という奇数はテスト対象に含まれる.このノードは子どもを持たないので,ルートノード1の直下の子ども枠の中で配置上の制約を持たないいわば,フロート状態にあると言える.ただし,このTC3000-1サンプルを読み出すためには,MAXIMALGRAPH::MergeUpperSegmentで同一セグメントと認定されるための条件を緩和する必要があった.

MergeUpperSegmentでは,対象結婚枠の「結婚点一致」という条件が成立すれば,結婚枠とその吊元である親ノードを同一セグメントと認定する.正確には,「誤差の範囲で結婚点が一致し,かつ吊り位置の子ノードの人名枠と親ノードの人名枠が水平座標でみて交差していること」という条件だ.現行では「誤差の範囲」として3という数が採用されている.これを12まで緩和することでようやく解に収束した.

image

このサンプルにもう一度5461を1の子ノードとして追加すると,再び収束不能状態に戻ってしまう.ただし,ゼルコバの木では子ノードは兄弟ノードの末尾にしか追加できないので,末弟の85の後ろに続くことになる.この図式はオリジナルのTC3000よりは容易に解けそうに見えるのだが,なぜか失敗している.どのような機序でこのような無限ループが発生しているのかを解析したいのだが,何せサンプルが大き過ぎて手も足もでない.というか,途方もなく時間コストばかり浪費するものになってしまう.「手に負えない問題 (intractable problem)」の典型だ.

このような場合には「事象を再現する最小サンプル」を作成するというのが定石だ.そこで,手始めにまず,TC3000-1上で末弟ノードの85とルート1の親子関係を切断してみた.この図式はloopcount=42で描画できる.この図式にノード1の子どもとして「5461」を追加する.これをTC3000-1+1とする.これで,この系図の登録カード数は4720点に戻った.末弟ノードの「85」は1との親子関係は切断されているが,系統としては残っているので,もう一度1の子どもとして復活させると事象が再現してしまうことは明らかなので,別のカードとして性別が女子のカード「85」を追加してみる.これで登録カード数は4721点になるが,問題なく描画できた.この図面をTC3000-4722としておこう.

▲アプリ終了しようとして,下記のエラーが出た.このエラーは前にも見たことがある… メニューバーの「名前検索」に関係するものだ.

image

TC3000-4722の女子カードの「85」に子ども「X」を追加したところ,事象が発生した.つまり,事象を再現するためには,かならずしも「85」の長い子孫系は不要ということになる.⇒試してみよう.TC3000-1+1を開いて,「85」の直系血族を全削除してみる.⇒ハングしてしまったようだ.「85」はこの図面では別系統(孤立した系列)になっているので,描画には影響ないはずなのだが…制御を失っている.⇒1点づつ削除ですべてのカードを削除することはできた.カード数は4618点.このファイルをTC3000-4618.ZELとして保全しておこう.

▲TC3000-1+1から「85」の系統を全削除しようとしてハングした.

ルート1の子ども枠にもう一度「85」を追加して4619点とする.これをTC3000-4619.ZELとして保全しておこう.

image

この図面では「5461」と「85」はともに子どもを持たないので,前方の「5」に隣接するところまで前方配置されている.

image

この状態で「85」に子どもを一人追加してやれば事象が発生することは明らかだ.「5461」と「85」が並んでいる必要はないと推定されるので,「5461」を削除し,その上で「85」に子ども「X」を追加してみる.⇒確かに事象が発生する.TC3000-4618.ZELの「5461」に子ども「X」を追加しても同じことが起こるはずだ.⇒再発する.元の図面の裾の方をカットしてみた.かなり大きい部分をカットしているが,今回は問題なく削除できた.3637点になったので,これも保全しておく.

image

▲全削除したあとの画面下部のステータスバーで表示カード数の値が変化していない.全カード数は変化している.⇒TC3000-3637.ZELを開いてみたら,カード数は3638/3638になっている.つまり,全カード数の表示も間違っている.

TC3000-3637.ZELは実際には3638点なので,TC3000-3638.ZELとリネームしておこう.このサンプルの「5461」に子ども「X」を追加しても,事象は発生しなかった.つまり,このサンプルの裾刈りは少し過剰だったようだ.もう一度,TC3000-4618.ZELに戻ってカットし直してみよう.今回は少し控え目にしてみたがどうだろう?点数は4382と出ている.⇒ダメだ.やはり描画できてしまう.

image

少なくとも結婚枠が接触しているポイントはすべて残す必要がある.

▲拡張選択でマウスをウィンドウ枠外までドラッグしてスクロールしているように見えるが,実際には同じ場所に留まっている.

!とんでもないことが起こってしまった!ノードを削除してより簡略なグラフを作っている途中で事象が発生してしまった.少なくとも4165点より小さな木だ.この作業の途中で孤立点が2点発生していたので,何か操作を誤っていた可能性はある.せめて,そのノード番号だけでも控えておけばよかったのだが… いずれにしても,この事象はグラフの大小にはよらないということだけは確実だ.だとすれば,もっとずっと小さい反例サンプルを作れる可能性もあるということだが…

!助かった!タイムアウトでブレークしてくれた.

image

いや,三極検定にはタイムアウトはない.ループカウントオーバーだ.デフォルトではかなり大きい数になっているが,放置しておいたので,最後に脱出できた.⇒これで少しだけ小さい反例サンプルを手に入れることができた.カード数は4154だ.CT 3000-4154.ZELとして保存した.このサンプルではルート1が孤立ノードになっている.

image

「5461」の子どもとして追加した「X」は入っていない.1の子どもは5人で末尾の子ども「5461」は子なしだ.このカードを削除してみよう.⇒カウントオーバーになった.つまり,このカードは障害の原因ではない.しかし,出てきた図面には1しか表示されていない.⇒もう一度試してみよう.⇒今度は1枚だけカード数が減った図面になった.一応これも保存しておこう.CT 3000-4153.ZELだ.それにしても,なぜ1しか表示されなかったのか?⇒親族図だった可能性がある.実際,親族図(直系血族図)としたらそういう図面が出てきた.

image

ただし,実際には1にはまだ4人子どもが残っているので,図面としては間違っている.ループカウントオーバーで強制ブレークしているためとも考えられるが…それでも,おかしい.⇒全選択すると一覧表上では全選択状態になる.⇒倍率が100%だったためだ.倍率100%では1の周辺には何もない状態になる.兄弟連結線が描画されていないのはおかしいが,それはバグということかもしれない.「画面に合わせてズーム」で全体が見えるようになる.1の水平線は消えているが,それ以外は表示されているので,配置上の問題かもしれない.

「5」と「1」を切断したら,ノーマルに描画できた.ただし,親族図から全体図に切り替えて,下記のエラーになった.

image

これは単なる偶然だ. (snum == 96423)で停止するというパッチが入っていた.しかし,「1」→「5」を切断するだけでここまで形状が変わるのはなぜだろう?

image

「5」の系統を全削除してみよう.⇒またレッドラインオーバーになった.どうもなんだか訳がわからないことになってきた.「5」の全系統を削除したつもりなのだが,まだ「5」の子どもたちが残っている.削除操作は系統並び替えに先行して実施されているはずだから,系統並び替えでエラーになる動作の影響を受けていないはずだが…しかも,「5」自体残って,遠いところで孤立点になっている.前にも「全削除」でハングしたことがあるので,「5」とその下流(の一部)が残っているのは,「全削除」のバグである可能性はある.

image

このグラフは4017点だ.これも保存しておこう.⇒何か操作を間違えたのだろうか?「5」の系統を削除したつもりだったが,「1」が消えてしまっている.この4017点のノードはすべて「5」の子孫だ.おそらく「全選択」の誤動作と思われるが,図面を縮小表示しているので,「誤操作」の可能性もある.いずれにしても,この図面の先祖ノードが「5」であることは間違いない.「5」には子どもが6人いる.{ 13, 213, 3, 3413, 53, 853 }だ.5→853の関係を切断してみよう.

▲全削除ではなく,1点づつ削除するようにしてみたが,どうも一部やり残しが出ている模様だ.

▲ステータスバーの基準カード,現参照カードの値が実際と一致していない.⇒上記,表示カード数の更新の問題と同根だ.

3997点まで縮小したが,まだレッドラインは止まらない.先祖ノードは「5」に変わっている.一旦これを保存しておこう.「5」にはまだ,子どもが5人いる.末弟の53の系統を削除してみよう.⇒「53」の系統には1931点のノードが含まれるが,その血統図自体壊れている.これ自体が反例サンプルなので,「画面上のカードを保存」で保全しておく.ファイル名は53直系血族図.ZELとする.

他の系統も調べてみることにする.その前に「53」の系統を削除してしまおう.⇒SeriaiizeHeaderエラーが起きた.

image

緊急退避ファイルに保存できないというメッセージが出ていたが.そのあと,SaveFamilyTreeのエラーが出始めて,止まらなくなった.

image

image

このエラーは切りなく出てくるので,やむを得ずアプリを強制終了した.デスクトップには反例サンプルファイルが生成されているので,多分最後の状態を復元できるだろう.⇒いや,反例サンプルは大量生産されているが,どれも3997点で55系統削除後のファイルになっていない.「53直系血族図.ZEL」を調理してみることにしよう.このサンプルは1931点だ.このサンプルには「5」が残っていて,その子どもが「53」だ.「53」には子どもが4人{ 141, 2261, 35, 565 } あるが,末子の「565」を切断してみる.⇒この系統はかなり小さく,4人しか入っていない.全削除してみよう.

53→35のリンクを切断して,MargTakeChildのエラーになった.

image

このエラーは繰り返し出てきたが,なんとか収まった.どうも「35」の兄弟2人を除いたカードはすべて「35」の血統のようだ.「2261」の系統というのも残っている.これも削除しておこう.⇒これで1908点になったが,まだ崩れたままだ.

image

このサンプルをCT 3000-1908.ZELとする.先祖ノードの「35」には,子どもが5人いる.末子の「93」系統を抹消してみる.このノードは3倍数だ.その上の「5973」も同様だ.第3子の「373」を抹消する.この系統の部分図を取ろうとして下記エラーになった.

image

部分図が動作していないので,全体図に戻って「373」の直系血族図を取ることにする.「373」の直系血族図は描画可能なようだ.一応これを保存しておこう.いや,「373」ではなくて,「35」の直系図になっていた.おかしい.いつの間にが描画可能になっている.「93」と「5973」を削除した時点で障害は解消していたのではないか?現在,1906点なので,確かに2点しか削除されていない.もう一度やり直してみよう.このサンプルも一応CT 3000-1906.ZELとして保存しておく.

2点削除してもレッドラインオーバーになる.⇒多分,35→373の切断で解けたのだろう.⇒確かにそのようだ.いまのところ,この1906点が最小反例サンプルということになる.これをBUG3000-1906.ZELとして確保しておこう.「23」の切断でも不良が起こる.ただし,これを復元して兄弟順位が1493→ 373→ 23のように変化すると,収まってしまう.子ども並び替えで元の順序に戻すと,再発するようになる.しかし,長子の1493の切断では不良は解消せず,むしろもっと甚だしいものになる.この系統はおそらく削除できるのでやってみよう.

これで先祖ノード「35」の子どもは「23」と「373」の2人だけになった.おそらく,そのどちらを切断しても収まるようになっていると思われるが,一応確かめてみよう.いや,その前にこのサンプルを保全しておいた方がよい.現在1900点なので,BUG3000-1900.ZELとして保存する.この図面は信じられないレベルでひどいものになっている.

image

予想通り,上記2点の切断では不良は解消する.また,兄弟順序を逆転した場合には,下図のような図面が出力される.それぞれの系統を比較するために,先祖ノードの「35」を抹消した図面を作ってみよう.

image

2つの系統を分離したときの図面も見ておこう.ただし,この図面には少し疑問がある.

image

左側の図面は系統並び替えを実施していないように見える.確かにそのようだ.左の系統の先祖ノード「373」で系統並び替えすると,下図のようになり,しぼんでいた風船に空気を入れたように急膨張する.

image

系統並び替えは始系列だけが対象となるのではなく,すべての系列を対象としているので,独立系統だから系統並び替えが掛からないということはあり得ないと思われるのだが… これについては別途調べなくてはならないが,いまのところは三極検定が収束しない問題に集中することにする.BUG3000-1900.ZELは横に広がった図面になっているので,縦書きで表示してみることにしよう.縦方向なら多少寸法を大きくすることができる.人名枠高も少し増加させてみた.

image

中吊りにしたらどう見えるのかも試してみよう.⇒大勢には影響しない.「23」と「373」の2系統は世代高さに大きな差がある.もし,この2系統の干渉による現象であるとすれば,下の方の世代をカットしても事象は再現できるのではないかとも思われるのだが,やってみたところそのようなことはなかった.⇒いや,中吊りの設定ではあるが,ある程度までカットはできる.ただし,それを過ぎるとやはり解けてしまう.

image

この図面は1303点だが,一応取っておこう.GOO3000-1303.ZELとしておいた.下図を見ても分かる通り,23と373の系統的な(水平的な)干渉などではないことは明らかだ.

image

かなりひどい図面が出たので保存しておこう.UNDOが機能しているかどうかを確認してみよう.⇒問題なさそうだ.ただし,一度以下のようなエラーが発生した.

image

反例サンプルは1353点まで圧縮したが,そろそろ限界に近い.BUG3000-1353.ZELを極小な反例サンプルとして保全した.

Windows 11 にアップグレードした

いきなりだが,面倒くさいのでWindows 11に移行することにした.画面の外観は,けばけばしたところが一掃されてわたし好みに近いものになった上,とりあえず通常の作業で支障を来たすようなこともなさそうだという見極めも付いたので,ともかくしばらくこれで運用することにした.大量のファイルコピーで以前より少し遅くなったような感じはする.エクスプローラで詳細モードで表示したときの行間が広くなっているのは気に入らないが,まぁ,しばらく使っていれば慣れるだろう.え,もちろん「ウィジェット」はオフになってますよ.

三極検定は個別系列内の要素の配置を決定する処理で,①結婚枠の吊り位置の調整,②結婚枠の衝突の回避,③要素間の距離を詰めてコンパクト化の3つの処理から構成される.水平セグメント検定と呼んでいるのは,この3番目のコンパクト化に関わるものだが,レイアウトを崩さずにコンパクト化するためにはあるまとまった要素のグループを同時に移動する必要があり,かなり技巧的なものにならざるを得ない.テスト回数3000のサンプルで起きている不良は水平セグメント検定の誤動作によるものと考えられるが,これを追跡するのはかなり難しい.

これまで使えていたSUW(ShowUnderWear)というデバッグ用のツールが使えない状態になっている上,巨大サンプルに対応してメモリ使用量を削減するために,水平セグメント検定で個別系列ごとに確保していた作業域をシステム全体で1個にしてしまっているため,セグメント番号による色分け表示というのもできない状態になっている.SUWというのはある種の例外処理で,プログラム実行中のどのタイミングでも(書きかけの)図面を画面に描画することができるという強力なツールだったのだが…SUWでは特定の系列を描画対象に指定することができるようになっていたはずなので,まだ工夫すれば何とかなる可能性がある.まず,それを追求してみよう.

コラッツ特注版ではメモリを節約するために,MAXIMALGRAPHのsegments配列とloosebox配列を外部に追い出して静的変数としているが,MAXIMALGRAPH自体は個別系列で保持するようになっている.しかし,MAXIMALGRAPHオブジェクトはセグメント検定中しか参照されていないはずだから,システムに1個あれば十分なのではないだろうか?少なくとも当面はセグメント検定を広域化するという構想は持っていないので,徹底してしまった方がわかり易いと思う.修正してみよう.⇒いや,やはりそこまでやるのはやり過ぎかもしれない.事後にsegmentcntなどを参照する場合がある.これらは個別系列で保持するしかない.⇒いや,そういう箇所は多くはない.というより,むしろ積極的にそうした方がよい.この修正に関わる箇所には以下がある.

  1. TRIBELIST::IsTribeComplete
  2. NAMEBOX::Group
  3. NAMEBOX::Group
  4. MARGBOX::clear
  5. NAMEBOX::Clear
  6. NAMEBOX::CLEAR
  7. TOPOLOGY::checkRIG
  8. Bobject::initialize
  9. Bobject::SetBranch

これらの箇所でセグメントを操作するのはむしろ危険だ.MAXIMALGRAPHを共有した上で,ReleaseSegmentを実行しないようにすれば描画時にもアクセスすることができるだろう.ReleaseSegmentの呼び出しは,①TRIBEBOX::HorizontalSegmentの出口,②TRIBEBOX::CheckMaximalSegmentの出口(単独で呼ばれた場合),③TRIBEBOX::CheckMaximalCompaction,④MAXIMALGRAPH::clearSegmentの4箇所ある.④は必須だが,①,②,③は止めることができる.

この修正をやってみて気付いたことがある.MAXIMALGRAPHを共有するということは,広域検定を実装するという方向に向かっているのではないかという点だ.三極検定は基本的に系列単位の処理なので,いきなりそれを広域化するというのは現実的ではないが,「共有」という語が「広域」を含意していることは明らかである.これで,セグメントのカラー表示はできるようになったが,SUWが動かないのでは始まらない.

どうも,どこか壊してしまったようだ.急に動かなくなってしまった.⇒TableSort.cppを差し替えて動作するようになった.TOPOLOGY::MakeLeastWindowで実行しているTRIBELIST::HeapTribeBoxesを関数の冒頭で実行するようにして,画面が表示できるようになった.理由はよく分からないが,これで良しとしておこう.SUWとセグメントのカラー表示が復活したので,デバッグを再開する.さて,どうやって追いかけたらいいだろう?

少し動作がおかしい.VS2027を管理者モードで起動しても立ち上がってこない.管理者モードでなければ起動できる.おかしなことに,タスクマネージャを開いているときには,管理者モードでも起動できるという動作になっている.管理者モードでないと不都合な場面というのはそれほどないので,運用的にはさほど不便ということもないような気はするが,管理者モードでないとデバッグ中にゼルコバの木のテーマを登録したりなどのこともできなくなる.タスクバーのアイコン→ 右クリック→ Visual Studio 2017→ 右クリック → プロパティ→ 詳細設定→ 管理者として実行をオンにして,ワンクリックで起動できるようになった.

SUWはまだ動いていない.HeapTribeBoxesの前にstackTribeGeneを実行することで描画できるようになった.stackTribeGeneは系列内部の描画要素を系列世代枠の集約する関数,HeapTribeBoxesはその計算を元に系列枠全体の矩形領域を計算し,それを集約して系図枠全体の矩形領域を計算するものだ.これで見ると,どんなことになっているのかをはっきりつかむことができる.

image

左端の孤立点は1だと思っていたが,違う.5, 5461,  85の3人兄弟だ.

image

1にはこの他に,1365, 21, 341 の合わせて6人の子どもがいる.このうち,1365,21 は3倍数なので,子孫につながるのは,341,5,5461,85の4つだけだ.このうちの3つは左端で孤立しているので,右側には1系統しかないことになる.上の図でおかしいのは,3人兄弟のうちの真ん中の5461の下に垂線がないことだ.実際,カード画面で見ても5461は子どもを持っていない.しかし,上記したように5461は3倍数ではないので,子孫がいなくてはならない.

ADLファイルで見ると,そもそも 5461 というカードの登録がない.15461というカードはある.⇒見落としていた.1の子どもの位置にある.ただし,5461を親とするレコードがない.これはかなりおかしい.つまり,ADLの出力自体に誤りがある.5には13,213,3,3413,53,853 の 6人の子どもがいる.85は,113,1813,453,7253 の4人の子がある.このADL出力の誤りを見つけなくてはならない.

明らかにGetTheNumberの論理には誤りがある.⇒いや,違う.5461はテストの対象となる奇数そのものだ.従って,5461が終端ノードであることは間違いない.もし,5461→1というパスがあるのなら,1に5461が直結しているのは間違いではない.実際,5461*3+1 = 16384 = 2^14だ.従って,ADLデータは間違っていない.確かにこの不良は結婚枠を一律中吊りにしていることに関係していることは間違いない.⇒中吊りを強制しないモードで走らせてみたが収束しない.

TC(Test Count)4000のサンプルでは中吊りの場合と同等の回数(loopcnt=32)で収束している.どうもこのTC3000というのは何か特殊なサンプルになっているのだろうか?この計算自体が収束しないある種の散逸構造的なトラップに陥っているのだろうか?

大きいサンプルの描画で不良が出ている

検証テストが出力する大きいサンプルの描画で不良が出ている.

コラッツ木ツールからZTを直接起動して,ImportEndで停止する.

image

このエラーはデバッグモードでは起きない.⇒Nリング上の特定のノードを検索するデバッグ用のコードが残っていた.

テスト回数4000では三極検定のループを34周で抜けているが,テスト回数3000では抜けられない.⇒どうもこれは,三極検定の欠陥が露呈したものであるような気がする.errcntは周回するごとに単調に減少してゆくが,ある点まで来るとまた跳ね上がってしまうという動作を繰り返している.以前はこれに加えて,ルートノード直下の結婚枠が結婚点不一致のとき,CheckMargBoxを実行しても移動量がゼロにならないという事象があったが,いまは再現できない状態になっている.

このサンプル自体が特殊なものになっているという感じもする.後で再試する必要があるので,このサンプルを取り分けておこう.⇒「CollatzTest.X.ADL」としておいた.

コラッツ木というのは紛れもなく「単純な木」なので,これが解けないとなると身も蓋もない.⇒中間の状態が見たいので,SUWを仕掛けてみたが,まったく描画できない.この三極検定はMainExprerimentの手前のTribeRelocationで実行しているので,TribeRelocationをまるごと止めてみたところ,ともかく描画することはできた.

image

ただし,この図を見るとどうもMainExprerimentの中では三極検定をやっていないように見える.CompleteTribeBoxは,①TRIBELIST::CheckCompleteList,②TRIBEBOX::StackTribeGeneから呼び出される.⇒MainExprerimentの中ではStackTribeGeneさえ呼び出されていない.⇒⇒出た!

image

出たのはよいが,少し形状が違い過ぎる.多分これは間違っているのだと思う.衝突検定が入っていないのではないだろうか?⇒正しく動作するためには,少なくともTREEVIEW::GetMaxMoveを実行する必要があるようだ.⇒この関数はTribeRelocationで実行される前にTREEVIEW:SetDispParmの中からすでに2回呼び出されている.

ここで更新されているのは,maxloopcountとMAXLOOPCOUNTの2つだけと思われる.これらの値はそれぞれ,20→2261,128→2261のようにかなり大きく変化している.maxloopcountは計算用の内部変数なので,外部に影響するのはMAXLOOPCOUNTの値だけだ.この値が急増するのは,この値がカード数と結婚数よりも大きい数になっているためだ.テスト回数3000のサンプルではこの値が1680になっている.

変動しているのは,ルート直下の結婚枠の幅だ.本来の配置なら30000くらいの幅があるはずのところが,313まで縮退し,また膨張するという運動を繰り返している.これでは収束するはずがない.⇒水平セグメント検定で失敗している.この解析はかなり難しいかもしれない…

検証テストカウント3200のADLサンプルでハングする

コラッツ木検定ツールとゼルコバの木が完全連動するようになったので,効率的にテストできるようになったが,検証テストでテストカウント3200を指定したときにハングしてしまうという問題が出ている.出力されたADLファイルを見ると,ノード数は3827で,ゼルコバの木のカード数上限の7000まではまだまだある.テストカウント3000では多少時間は掛かるが問題なく描画できているので,原因を突き止めておく必要がある.⇒極大セグメント検定でハングしているようだ.

いや,極大セグメント検定からは抜けているが,三極検定でループしている模様だ.CompleteはTRUEになっているが,perfectがFALSEのままになっている.ループカウントオーバーというのは見ているが,ループを一周するのに相当時間が掛かっているようだ.ループカウントが3646を超えるとレッドラインオーバーが発生するが,それまでに日が暮れてしまいそうだ.REDLINE=10に変更し,強制的にブレークするようにして描画まで進んだが,まだ,どこかで詰まっているところがある.画面の半分しか描画できないだけでなく,タイトルバーをつかんでウィンドウをドラッグすることもできない.

image

いや,まだ,計算は完了していない.画面は以下のように変わった.

image

しかし,制御はまだ戻ってきていない.水平スプリットの検査に手間取っているようだ.⇒スプリット検査を止めたら抜けてきたが,画面は上と変わりない.左上隅に小さな集落がある他は,広い範囲にごく短いヤブが生い茂った状態だ.系統が2分解し,ノード1を先祖ノードとする始系列ともうひとつの系列に分かれている.後者の先祖ノードは#1825のようだ.#1825の親は#1369だが,カードで見ると親なしになっている.#1369の親は#1027で子どもは#7301だけになっている.

結局,#1369→#1825という親子関係が失われているということになる.ゼルコバの木のダンプで見ると,このサンプルのカード数は5045,結婚数は3645.この数は上で「ADLファイルを見ると,ノード数は3827」としているのよりも少ない.ソースコードを見ると,最大カード数は0x2000=8192,最大結婚数も同じ8192だ.MAXCHILD=12となっているが,12人より子どもが大きいときには複数の結婚ページを使うことができたはずだ.MAXMARRIAGE 32となっているので,384人までの子どもを持つことができるはず…実際問題として,もっとも子どもが多いのは1の6人,いや,5にも6人の子どもがいる.

「ADLファイルを見ると,ノード数は3827」というのは,ADLファイルの行数が3827という意味であり,この数は結婚数と一致していなくてはならない.しかし,実際には3645というのだから,182個も少ない.これもかなりおかしい.結婚が切れているとしたらその子どもはすべて親なし,つまり先祖ノードになってしまうから,系列がその数だけ発生しなくてはならないと考えられるのだが…ともかく,ADLから変換したCSVを保全してチェックしてみることにしよう.⇒CSVでは

#1825,男,1825,,,,,,,,,,,,,,,,1,1,1,#1369,,1
#7301,男,7301,,,,,,,,,,,,,,,,1,1,1,#1369,,2

のようなレコードがあり,#1369→#1825という親子関係は活きている.#1369のもう一人の子どもである#1825は問題なく繋がっている.どういうことだろう?世代数には上限はなかったはずだが…始系列はmin=0, max=115とあるので,世代数では116世代ということになる.いや,この数字も奇妙だ.コラッツツールの画面ではTree height は96になっている.この数字が正しいとすれば,どこかで連結を誤っていることになる.このテストでは1~6399の奇数を検定したとなっている.

Total odd number count: 3200 というのは疑問のある数字だ.確かにテストされた奇数のカウントは3200だが,Total odd number には木に含まれるノード数を表示すべきではないか?いや,同じノードが何度も重複出現するため,それをこの場でカウントするのは無理だ.⇒Total odd number countを Tested odd number count に変更した.

問題のレコードは少なくともVBからはDLLに送信されている.TREEVIEW::sendUpdateDataではこのレコードを受信して処理している.LINKTABLE::ImportEndでは1825は父1369を持っている.⇒いや,違う.1825はImportEndでMakeMarriagePageに失敗している.⇒LINKTABLE::MakeMarriagePageの中で「上流で循環」が発生している.1825の曽祖父である1541の親が本来289のところ1825になっている.少なくともADL上ではこの間違いは起こっていない.CSVにもこの誤りはない.従って,インポート処理中の誤りと推定される.

どこかで誤動作しているのだろうか?LINKTABLE::ImportEndの2周目でこの値が入ってくる.⇒いや,違う.もっと前だ.ImportTableFuncで送信した1825の2番目の子ども9733のレコードを処理するところで誤動作している.LINKTABLE::GetRefnumが9733の参照番号を求めているのに対し,1541が返されている.上記したように,カードテーブルのサイズ上限は8192なのでそれを超える値に対し,ハッシュした代替値が返されたのだろう.⇒VB側でMAXPDB=7000になっていた.ただし,これは多分動作には影響していないと思う.おそらく,この後に本物の1541が登場し,その位置に座ってしまったのではないか?

どうも,この不良は手順が悪いためではないかという気がする.TREEVIEW::sendUpdateDataでは,breakNumberでは名前から参照番号を取り出しているが,この関数から呼び出しているGetRefnumでは,ハッシュ化を実行している.しかし,この参照番号はその場では登録されず,事後にまとめて処理されているため,ハッシュの衝突が発生しないので,重複登録が容易に発生する状況になっている.これを安全に実施するためには,breakNumberの中でカード登録を行うしかないのではないか?⇒対処した.これで一応画面は表示できたが,目を覆うような惨めな結果になった.

image

特にひどいのは,ルートノード1だ.画面の一番下にどこにもつながらない状態で孤立している.縦書きに戻してみよう.⇒三極検定のループカウント上限を10に設定した状態では上と同じ図面になってしまうが,上限を100まで上げたところ,ループカウント32で抜けてきた.

image

形状は以前「蝙蝠」と呼んでいたものとほぼ同型だ.カード数は5045点.カード数上限は8192となっているので,もう少し大きくしてみよう.テストカウント4000を試してみる.⇒三極検定のループカウント33で抜けてきた.カード数は6365になった.最大枝数は8,樹高は96.

image

前にテストカウント3000というサンプルを取っているので,取り直ししてみよう.⇒なぜだろう?レッドラインオーバーになってしまった.

image

障害の原因が左端に孤立したルートノード1であることは明らかだ.なぜ,こんなことが起きているのだろう?結婚枠をカラー表示してみると下図のようになる.

image

コラッツ特注版では結婚枠はつねに中吊りしているのだから,全体を左シフトして最上層の結婚枠中央に1が来るようにすればよいだけと思われるのだが… なぜそれができないのだろう?いや,もしかすると「結婚点の一致」という操作を止めていたかも…⇒試験的に止めていたこともあるが,戻している.CheckMargPointで毎回移動が発生している.移動量は変動があるが,つねに正で48960などの大きな数値だ.

修正をフィックスしてリリース版を起こした

修正をフィックスしてリリース版を起こしたが,ZELのアイコンをダブルクリックしてファイルを開いたとき,「基準ノード不在」が発生するという事象が残っている.開発環境でデバッグモードでコマンドライン起動できるようになっているが,そこではこのような事象は発生していない.つまり,リリース版とデバッグモードの動作が異なるものになっている.⇒リリースモードでコマンドラインを実行してみればよいのではないか?⇒おかしい,現象が再現しなくなった.⇒確かに,一つ修正を入れてからリビルドしているはずだが,すでに解消しているようだ.

コマンドラインで隣接リストのCSVがオープンできるとしたら,ダブルクリックでそうできてもよい.ただし,そのためにはCSVという拡張子をZELに登録しなくてはならない.CSVファイルは一般にはエクセルなどの表計算がデフォルトアプリになっているので,それを横取りするというのも多少問題がある.むしろ,隣接リスト専用の拡張子を決めてゼルコバの木で登録するようにした方がよいのではないか?現行ではゼルコバの木形式エクスポートも隣接リストも同じ拡張子CSVを使っているが,これも問題だ.*.ADLとしてみよう.ADLはActivities of Daily Livingで「日常生活動作」という意味だ.これには,移動・排泄・食事・更衣・洗面・入浴などの日常の生活動作が含まれる.わたしの関心のもっぱらの傾向からすると適切な拡張子であるような気がする.

このファイルが出力できるのは今のところコラッツ木生成ツールだけだが,隣接リストはテキストエディタでも簡単に作れるので,まぁ,よいのではないだろうか?⇒だいぶわかり易くなった.⇒アプリインストール時に*.ADLを登録するようにした.これでADLファイルをダブルクリックしてもコラッツ木が表示できるようになった.

ADLファイルはもともとCSVファイルだが,インポートしたときに内部的に一度ZEL形式のCSVフォーマット形式に変換し,それをインポートするという動作になっている.このため,ファイルを読み込んだあと,タイトルに表示される名前がこの一時ファイルの名前になっている.これはかなりおもしろくない.書き換えることは可能だろうか?⇒一時ファイルの名前を単純にADLファイル名の後ろに”.CSV”を追加するだけにした.これなら表示されてもそれほど不自然ではないと思う.

開発環境でリリースモードで走らせると空白画面になる.本来は新規カードが1枚表示されていなくてはならないところだ.⇒いや,コマンドライン引数を渡さないようにすれば,デバッグモードでも同じ動作になる.どこか壊してしまったのだろうか?⇒コマンドライン起動の処理を一箇所に統合するために相当書き換えを行っているが,コマンドライン引数なしの場合の処理を落としていた.

いい感じになってきた.ほとんど「最強のコラッツツール」と言ってよいと思う.*.ADLのアイコンをダブルクリックするだけでゼルコバの木が立ち上がってくるというのもうれしい.このツールには5つの機能があり,それぞれ一般木と仮想木を扱えるようになっているから,5×2=10種のファイルが出力されることになる.まずは,これらの出力ファイルを一通りチェックしておこう.

ゼルコバの木が立ち上がっているのに,タスクバーにアイコンが出てこない.代わりに何か白い枠のようなものが出ている.EXEのアイコンとMDIウィンドウのアイコンは出ている…タスクバーに古いバージョンのアイコンが残っていたためだ.⇒問題なさそうだ.

A面の出力は問題ないと思う.左が一般木,右が仮想木だ

image image

▲コントロールキー+リールでズームができるようにしたい.また,ズームボタンは押し続けていると段々速くなるようになっているようだが,初期の動作ももう少し速くてよい.

アドレス変換でエラーが出た.分岐枝リストは「1. 1. 2. 1. 1. 1. 1. 1. 1. 2. 1. 1. 1. 2. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 2. 1. 1. 1. 3. 1. 1. 2. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 2. 1. 1. 1. 1. 1. 2:1」でコラッツ数列が「53261」から出力したものだ.⇒一見したところどこも問題ないように見えるのだが… ⇒一般木でテストしていたためだ.現行では一般木では拡張アドレスをサポートしていない.というか,一般木の場合のアドレスはつねに実アドレスになるから,拡張アドレスという概念はない.また,そのアドレスコードの示す経路中に3倍数が入っていれば,そこでアドレスコードはカットされる.

一般木上では「53261」のアドレスコードは「1. 3. 1. 1. 2. 2. 1. 1. 2. 1. 1. 1. 2. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 2. 1. 1. 1. 1. 1. 1. 1. 1. 1. 2. 1. 1. 1. 4. 1. 1. 2. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 2. 1. 1. 1. 1. 1. 2. 2」のようになる.長さは同じ60だ.ADLファイルの名前に木の種別を入れておいた方がよいのではないか?一文字でよいと思うが,一般木をG,仮想木をV,完全木をFとしてみよう.⇒しかし,今の誤操作はコラッツツール上のもので,ADLファイルとは関わりがない.⇒現行では仮想木に切り替えると出力枠の背景色がブルーになる.分岐枝リストのフレームも同色に切り替えた方がよいのではないか?

悪いアイディアではないが,効果は限定的だ.コラッツ数列のリスト出力を転用してアドレス変換することが多いが,リストに残っているのが,どちらのモードで出力されたものかは判別できない.⇒とりあえずは,現行のままとしておこう.ファイル名にモード種別を追加するという修正は入れておいた方がよいと思う.

幹線木の関係がおかしい.一般木も仮想木もコラッツ番号と合っていない.⇒サンプリングが悪いのではないか?多分相応していない組み合わせになっているのだと思う.サンプルを取り直した方がよい.23637という数字をターゲットとして取り直してみる.⇒問題なさそうだ.

image

コラッツ数列とアドレス→ノード番号は逆順になっていて,幹線木の軸はアドレス→ノード番号の系列と完全に一致している.これでB面4機能のうちの3つの出力が確認できた.あとは検証テストだけだ.2^3という範囲で1~15までの奇数を含む最小のコラッツ木を出力してみた.

image

終端ノードは一般木では3, 9, 15の3つあるが,仮想木では3はコラッツ数なのでパスの中間の位置に出現する.表示されているノード数は一般木が12で,仮想木は10だ.13と53は仮想木上には表示されない.53は範囲外だからよいとしても,13は[1, 15]の区間に入るので,表示されてもよいのではないか?拡張アドレスコードを使えば非コラッツ数の位置を示すことができるのだから,表示できないというのはおかしい.

13は3の兄弟ノードだから,5の下に来るべきだ.53も同じく3の兄弟だ.コラッツ数列取得で確認すると,13には「1. 1:1」というアドレスが割り当てられている.やや,53でも「1. 1:1」が出力されている.これはおかしい.一般木で見ると5の子ども枠には,{3, 13, 53, 213,…}が入っている.従って,3:={1. 1:0},13:={1. 1:1},53:={1. 1:2},でなくてはならないと思うのだが…実際,アドレス→ノード番号にこれらのコードを与えてノード番号を出力すると,{1. 1:1}→13,{1. 1:2}→53,{1. 1:3}→213が出力される.従って,アドレス→ノード番号は正しく動作していると言ってよいと思う.明らかにコラッツ数列取得処理はどこかで間違えている.

あれ?!今度は正しく動作している.読み間違えた,というか,どこかで操作を間違えたのか?このツールはあちこち操作するところが多いので勘違いを起こし易いかもしれない…ともかく,Get Collatz sequenceとAddress to numberが完全に逆演算になっているというところが肝心なところだ.この2つが正しく動作していれば,検証テストは自ずと正しく動作するはずなのだが…CSVに変換する前のADLの中身をテキストエディタで調べてみよう.一般木の方は,

1    5
11    7
13    17
17    11
23    15
35    23
5    13    3    53
53    35
7    9

のようになっている.これは正しい.仮想木を見てみよう.

1    5
11    7
17    11
23    15
3    17    35
35    23
5    3
7    9

確かにこれは,ゼルコバの木の描画と一致している.つまり,ADLの出力に欠落があるということになる.検証テストの出力は,コラッツ数列ではなくアドレスノード番号で出力していたような気がするのだが,違うだろうか?⇒確かに,そうなっている.NumberStreamに書き込んでいる場所で,かならずVerifyStreamに書き込むようにしていれば同期は取れるはずだ.NumberStreaming,TruncatedStreaming,VerifyStreamingはかならず排他的になっていなくてはならないのだが,どこかで崩れているということはないだろうか?

VeriVerifyStreamへの書き込みは2箇所でいずれもGetTheNumberの中だ.⇒おかしい.NumberStreamへの書き込みとVerifyStreamへの書き込みの位置は完全に一致している.そもそも,検査の対象から弾かれているという可能性が高い.⇒確かにその通りだ.コラッツ数しか対象としていない.⇒修正を入れてみたが,相当おかしな結果になった.⇒いや,間違えていた.Odd numberに大きな数字が入っていた.1から始めるようにしたら,カード数は11点になった.

テスト区間は[1, 15]でその間の奇数はすべて出力されている.53という数は入っていないが,この数は区間外であり,それがなくても連結性は保たれている.これでようやく正しく動作するようになった.区間を設定するのに2のべき乗を使っているというのはちょっと使い勝手が悪い.もう少し細かい範囲で調整したい.2のべき乗を使ったのは,大きい数字を入力するのが面倒だったからだが,やはり整数で指定するようにするべきだった.⇒これはやはり変更した方がよいと思う.⇒できた!

テストカウントを16で走らせてみた.

image

区間は1~31でカード数は53,1~31までの奇数はすべて入っている.1から一番遠いノードは27で距離は41ある.仮想木ではカード数は51で2枚少ない.この2つの図面を合併(追加読み込み)して,共通部分を取って部分図とすれば,一般木と仮想木がどのように交差しているかを見ることもできるだろう.テストカウントは巨大数になるので,かなり大きなテキストボックスが必要になる.Odd numberと同寸では画面に収めるのが難しい.

image

なんとか収まった!

image

これで大体仕上がったのではないかと思う.⇒テストカウント4000のコラッツ木を試してみたが,ハング状態になって抜けてこない.3000くらいまでは難なく描画できていたのだが… ゼルコバの木の上限は多分7000だったと思うので,仮に上限をオーバーしてもそこまでのグラフは描画できなくてはならないのだが… 計算自体は数秒で完了している…3000まではそこそこの時間で描画できるが,3200になるとほとんどハングしている.下図はテストカウント3000,ノード数4720のコラッツ幽霊木だ.前は「蝙蝠に見える」と言っていたのだが,点数が増えると,むしろ「炎」のような感じになってきた.

image

ゼルコバの木はだいぶ前に「Zelkova tree 2022」に改名しているが,まだどこかに「Zelkova-tree 2021」というのが残っているようだ.⇒AssemblyInfo.vbというファイルに入っている.このファイルはシステムが自動的に作っているものだと思うが…

なぜだろう.今回はテストカウント40000でカード数7000というADLが難なく開けた.考えられるのはカード数が不足しているため,グラフがぶつぶつの非連結になったため,却って速く描画できているのではないか?という感じだが…このグラフの木の高さは129だが,実際の描画はもっと低い木になっている.木というより,ヤブになってしまっている.実際,こんな図面になっている.

image

コラッツ木生成ツールから直接ゼルコバの木を起動する

コラッツ木生成ツールからゼルコバの木を直接起動することができるようになった.かなり楽しい.

最初にUPした動画は34MBで,アップロード時にエラーになったので,Windows 10のムービーメーカーで画質を落として(電子メール用として)保存したら,5MBになった.WordPressはデフォルトでは30MBが上限になっているようだ.ただし,後でメディアライブラリをチェックしてみたら,ファイルはアップロードされていた(アップロードされているかもしれないという注意は出ていた).まぁ,それほど高画質の映像が必要な訳ではないので,これで十分だろう.スマホで撮影するのではなく,画面をそのままキャプチャできればよいのだが… ⇒多分,探せばフリーソフトで,あるとは思う.

アプリ終了するとき,「データは更新されています.保存しますか?」は不要⇒コラッツ特注版では基本的に保存機能はサポートされないのだから,このメッセージを出す必要はない.コラッツ木生成ツールからゼルコバの木を起動した場合,毎回新しいEXEが立ち上がるので,画面を閉じるたびにメッセージが出るのはうるさい.⇒対処した.

ゼルコバの木のデバッグモードでコマンドライン起動するような仕掛けを組み込んだ辺りから,何か,具合が悪くなっている.カスペルスキーが毎回複数の「脅威」を検出してくる.どういうタイミングなのかはわからないが,ユーザーフォルダのAppDataの中にEXEが複製ないし生成されていて,それをカスペルスキーが「脅威」として検出するという事象だ.デバッグしようとしているEXEとそのPDBをVSが取り分けて実行しているということは考えられるが,毎回暗号化した一時フォルダに格納しているので,おそらく除外リストに登録しても効果がないのではないかと思う.⇒除外リストでフォルダが指定できればよいのだが…

ともかく,VS2017を修復インストールしてみよう.以前にも似たようなことはあったが,そのときは修復で収まっていた.

アドレス変換でアドレスコードが間違っているためエラーになったときにも,エクスポートパネルが表示される.⇒対処した.

▲エクスポートパネルからゼルコバの木を立ち上げたとき,系図画面の位置を覚えていない.⇒外付けモニターで閉じた場合は記憶されている.いや,今度は覚えていた.⇒また,戻ってしまった.この配置はドッキングモードの位置とサイズに似ている.

分岐枝リスト空の状態でAddress to numberを実行し,ZTをオープンして「インデックスが範囲を超えています」というエラーになった.⇒「分岐枝リスト空」という状態はノード1を意味している.出力フレームには「1」が表示されているので,動作的にはこれで正しいはずだ.しかし,CollatzNumber.csvの中身は空になっている.幹線木でも同じエラーになる.⇒分岐枝リストが空の場合は無処理でループから離脱している.ダンプリストへの出力はループの外で実行しているため,「1」が表示されているが,CSVファイルへの出力はループ内で実施しているため,無動作になっている.⇒対処した.

▲IDEをクローズボックスで閉じようとして例外が発生した.このパネルは前にも出ている.

image

コマンドライン引数を与えてデバッグする

コラッツ木生成ツールとゼルコバの木が一体化し一つのアプリのように連動するようになった.これはとても重要な一歩だが,あちこちで不具合も見られる.ともかく,このような使い方をすることに決めた以上使えるようにしなくてはならない.一つづつ潰してゆくことにしよう.

まず,系統並び替えの入口で基準ノード番号が0になっているという点から見てゆくことにする.これは結局,基準ノード番号の初期値が未定ということを意味していると考えられる.

通常系図データファイルには基準番号が保存されているので,それを取り出せば済むことだが,インポートした場合にはどこでそれを決めたらよいのだろう?⇒CSVファイルをファイル→隣接リストのインポートで読み込んだ場合には問題なく動作している.つまり,コラッツ木検定から直接ゼルコバの木を起動した場合にだけ起きる不具合だ.ノーマルなインポート手順と比較してどこか抜けているのだろう.

コラッツ木検定からゼルコバの木を直接オープンする場合はコマンドラインで引数が渡される.このようなコマンドラインからの起動は,「ファイルオープンテスト」でも行われているが,特に問題は起きていないように思われる.MDIForm_Loadの出口でコマンドラインを処理していところで,OpenFileProc(, OPENMOD.FULLIMPORT)の戻り値をCurrefnumに格納するようにした.

小さいサンプルでは問題は起きていないが,高さ60くらいになるとスクロールバーと実際の図面が合わなくなる.しかし,ズームなどの操作で画面を更新した後は,問題なく描画できる.⇒ズームアウトで縮小表示すると正常描画されるが,拡大方向ではまだおかしな図形が出てくる.人名枠は表示されても名前が出なかったり,一部の枠線が描画されなかったり.クリッピングの問題である可能性は高いような気がする.「再描画」を実行しても変化しない.⇒ウィンドウサイズを変えると正常に戻る.何かもう一つアクションが足りていないように思われる.

おかしい.リリース版に戻したらまた「基準ノード不在」が出るようになった.ツールの動作にもおかしいところがある.アプリを開発環境から起動して,インポートを実行した後,アプリを終了しても開発環境に制御が戻ってこない.また,エクスポートパネルはTopMostで実行しているのに,アプリ終了できてしまうというのもおかしい.⇒メインフォームをTopMostにしていたためだ.しかし,修正してもアプリ終了で制御が戻らないという現象は変わらない.

エクスポートパネルをHideで閉じていたので,Closeに変更したが,変化しない.FormClosedイベントは処理されているのだが…FormClosedの中で明示的にEndを実行することで正常終了できた.

基準ノード不在はOpenFileProcを実行する前に発生している.しかも,その後,OpenFileProcは2度呼び出されている.最初はmode=0,つぎにmode=2で呼び出される.mode=0はOPENMOD.ORDINARYだ.どこから呼び出されているのだろう?⇒TREEVIEW::GetScrollValueではフェーズがDRAWSTAGEのときはTopologicalSortを呼び出すようになっているが,CHAOTICSTATE未満では呼び出さないようにした.これで初期起動のときTopologicalSort呼び出しが重複するのは回避できるようになったが,今度はインポートのとき.TREEVIEW::GetViewSizeでbasenum=0で停止するようになった.この辺りはDLLを並行してデバッグできないと手も足も出ない.

.NETアプリとDLLを同時にデバッグするということは可能だろうか?しかも,一方はVS2019で他方はVS2017だ.2つのプロジェクトを完全に合体させて一つのソリューションに仕立てるということも場合によっては不可能ではないとは思われるが… .NETアプリは別として,EXEをコマンドプロンプトから起動してデバッグすることは可能なのではないか?⇒VBのプロパティ→デバッグ→開始オプション→コマンドライン引数で引数を渡すことができる.これは便利だ!これでコマンドライン起動時も完璧にデバッグできる.

上記のGetViewSizeのエラーは,GetScrollValueの中で起きている.このエラーを無視しても動作上特に問題は見られないので,入口でゼロ復帰するようにしておく.OpenFileProcが2度呼びされるというのは,MDIParentのロード時に,OpenTestでない場合にはInitFileを実行しているためだ.この処理は,ZELファイルのアイコンをダブルクリックで起動した場合に必要となるため,省くという訳にはゆかない.OpenTestはコマンドライン引数にディレクトリが入っているので識別できるが,ダブルクリックの場合には拡張子を見るしかない.

インポートの場合はInitFileを実行しないようにしてOpenFileProcの二度呼びは回避できるようになったが,結果はなお悪い.51725のコラッツ数列で5点のチェーンになるはずが,後ろ半分が千切れている.InitFileの中には何か必要な処理が入っているようだ.⇒副作用としては新規カードを一枚初期表示するというだけのなので,ここのところを詰めるのは後回しにしておこう.

画面外の部分が描画できないという問題は,インポートだけでなく,ダブルクリック起動の場合にも起きている.かなり厄介な問題だ.しかし,メニューコマンドからのインポートや保存したZELファイルのオープンなら問題なく開けているので,何か些細な漏れがあるものと思われるのだが…⇒何か,画面が暗幕のようなもので遮られているような感じだ.初期表示されていない部分をスクロールで見えるようにして,マウスドラッグで拡張選択しようとしても選択枠線を描画することができない.しかし,描画できている領域から始めると選択矩形をドラッグに従って描画できる.しかも,このとき「不可視領域」までの選択矩形が入り込んだ途端,すべての図形が見えるようになる.謎としか言えない.

何が問題かと言えば,「コントロールサイズ」だろう.コントロールサイズは基本的に系図外枠サイズに一致しているはずなのだが…

▲ZTの実行中,開発環境を落とそうとしてフェーズエラーが発生した.

image

その後,プロジェクトを1/5アンロードしたところで例外が発生した.

image

System.ArgumentException: 値が有効な範囲にありません。
    場所 Microsoft.Internal.VisualStudio.Shell.Interop.IVsSolutionBuildOrderPrivate.GetBuildOrderList(Boolean fForceRecalculation, UInt32 cProjects, VsProjectBuildOrder[] pBuildOrder, UInt32& pValidationId)
    場所 Microsoft.VisualStudio.ErrorListPkg.Shims.TaskListBase.RecalculateProjectRank()
    場所 Microsoft.VisualStudio.ErrorListPkg.Shims.TaskListBase.OnEntriesChanged(Object sender, EntriesChangedEventArgs e)
    場所 Microsoft.VisualStudio.Text.Utilities.GuardedOperations.RaiseEvent[TArgs](Object sender, EventHandler`1 eventHandlers, TArgs args)
— 直前に例外がスローされた場所からのスタック トレースの終わり —
    場所 Microsoft.VisualStudio.Telemetry.WindowsErrorReporting.WatsonReport.GetClrWatsonExceptionInfo(Exception exceptionObject)

デバッグ中にシャットダウンしようとしたのが悪かったのかもしれない…⇒どうも,かなり状態が悪くなっている.VS2017が立ち上がらなくなってしまった.⇒シャットダウンして再起動で立ち上がった.カスペルスキーから2件の「脅威」を検出したという警告が出た.

image

「ユーザに損害を与える目的で悪用される可能性のある正規のソフトウェア」というのはこれまでも何度も出現しているが,今回のは捕獲された場所がマイクロソフトの管轄域であるというところが違う.いずれもZelkovaTree2022.exeで作成時刻は午前1時36分.開発用フォルダに残っているEXEはリリース版が23:00,デバッグ版が23:37でそれより後に作られている.捕獲場所には同じ時刻に作られたPDB(シンボルファイル)も入っている.「解決」ボタンで削除できないため,「駆除して再起動」になった.カスペルスキーはこれを「特別な駆除」と呼んでいる.再起動すると以下のパネルが表示された.

image

このパネルはいつも「特別な駆除」の後に出てくるようだ.これを閉じると,今度はカスペルスキーの完全スキャンが始まる.2TBの外付けHDDが接続したままなので丸一日掛かるかもしれない.カスペルスキーのスキャンはウィンドウを閉じてもバックグラウンドで実行を続けるので一旦閉じておこう.一応捕獲場所をチェックしておこう.⇒一つはフォルダごと削除されているが,もう一つはProjectAssemblyフォルダの中にPDBが残っていた.⇒障害が再現できるかどうかやってみた.デバッグを開始して,ZTがCSVをインポートした後,開発環境をクローズボックスで閉じるという操作だが,問題なく閉じることができた.

MDIForm_Loadでコマンドライン引数で実行するところの論理を少し整理して見通しよいものにしておきたい.できれば,すべてを一つのOpenFileProcの中で処理したいのだが…

▲OpenFileProcではMakeNewObjectで毎回Child配列を初期化しているが,不要なのではないか?ClearChildで間に合うはずだ.

初期表示された画面上のカードをクリックないし選択することでノーマルな状態になる.これはどういうことだろう?カードのクリックというのはOCXに入り,そこから折り返してVBに戻るという動作になっているはずだから,OCX側からの何かしらの応答が必要ということではないだろうか?再描画というのは逆にVBからOCXへの伝達になるので,何の効果もないのではないか?⇒系図画面上でクリックイベントを発生させれば,擬似的に動作するのではないか?

コラッツ木生成ツールから直接ゼルコバの木にエクスポートする

コラッツ木生成ツールから直接ゼルコバの木にエクスポートできるようになった.「隣接リストをエクスポート」オプションをオンにしておくと,メッセージパネルが開いて保存先のファイル名を表示するようになっている.このパネルにボタンを追加して『ゼルコバの木』を起動し,コラッツ木をその場で確認できるようにした.これはかなり楽しい.

image

しかし,これができるようになったために,いままで隠れていたボロが一挙に噴き出してきた.たとえば,59871のコラッツ数列生成では88点の系図が出力されるが,画面の大部分が真っ白になってしまう.アドレス変換ではこれを逆順に出力しただけのものだが,こちらは正常に表示されている.幹線木では262点のグラフになるが,やはり大部分は真っ白になっている.これは多分,横書きの問題ではないかと思われる.⇒いや,縦書きにしても同じ現象が起きる.これはかなりまずい.

どこかで描画領域の管理に失敗しているようだ.縦書きの場合,画面では最上層が表示されているのに,スクロールバーのノブはボトムの位置にある.スクロールバーを上に上げれば画面は真っ白になってしまう.しかし,アドレス変換では同じものが問題なく描画できているので,問題はゼルコバの木本体ではなく,インポート処理の部分にあるように思われる.データ自体はいずれの場合でも問題なく読み込まれているようだ.⇒いや,データ自体がおかしくなっている可能性がある.ZELで保存して再読み込みしても同じ現象が起きる.

いや,今度は読めた.何か操作を誤っているのだろうか?⇒少なくともアプリを一度落として再起動してやればZELファイルを読み込むことができる.インポートした直後に読み込んだ場合は,画面が更新されていないという感じだ.リリース版の動作を確認してみよう.今度はインポートしたときに,「基準ノード不在」というエラーが出るようになった.⇒アプリからZTを起動してインポートするのではなく,CSVをZTのインポートコマンドで読み込むようにすれば何の問題も起きない.⇒なぜだろう?完全に問題なく動作するようになった(「基準ノード不在」というエラーは残っている

縦書きでは問題は解消しているが,横書きのコラッツ数列ではまだ前方が白紙になる.⇒画面の描画領域の問題である可能性はある.確かに大きい図面だとメモリ描画環境でメモリ不足が生じて生描画に切り替わる場合がある.一度「画面に合わせてズーム」まで縮小するとその後は拡大しても描画上の問題は発生しなくなるようだ.メモリ描画環境の操作に関わりがあるような感じもする.少なくともデータ(読み込み)の問題ではない.⇒系統並び替えの入口で基準ノードが0になっている.



「拡張コラッツ問題」は一般には成立しない

前稿(2022/04/06)には,2つの式が記載されている.

ノード番号:={長子ノード番号}.{兄弟順位}      (1)
長子ノード番号:={親ノード番号}.{長子識別子}    (2)

これら2式が正しければ,

ノードN:={地番1}.{地番2}.{地番3}…{地番k}

のようなアドレスコードが可能となり,すべての自然数はその内部にコラッツ木の(暗号化された)アドレスコードを持っているということになると予測される.前稿ではこれら2式について,

『(1)式はNの4進数表記からデコードされ,(2)は3N+1の2進数表記によってデコードされる.3N+1という計算は3進表記した値を1左シフト(した上で1をプラスする,つまり1を右端に連結)することによって得られるから,「演算(代数的操作)」というよりは,「文字列操作」として(形式的に)実行可能であると推定される.』

としているが,果たしてそう言い切れるのだろうか?『(1)式はNの4進数表記からデコードされる』という部分は,3月3日付けのログ「コラッツ国の公用語は4進数である」で説明しているように,ほぼ確実であると言えるが,『(2)は3N+1の2進数表記によってデコードされる』とすれば,以下のような類似問題が成立することが予想される.

拡張コラッツ問題:kを任意の奇数(定数)であるとする.任意の自然数Nについて,Nが偶数ならば2で割り,奇数であればk倍して1を加える操作を反復すると,かならず1に帰着する

この問題は,Wikiの「コラッツの問題」というページの「類似の問題」という項目に以下のような形式で採り上げられている.

「任意の正の整数 n に対して

  • n が偶数の場合、n を 2 で割る
  • n が奇数の場合、n に 2m – 1 (m ≥ 1)をかけて 1 を足す

という操作を繰り返すと、有限回で 1 に到達する」

m=1の場合,この命題が成立することは容易に示すことができる.m=2の場合がコラッツ問題だが,m=3の場合,つまりk=5の場合には

13→ 66→ 33→ 166→ 83→ 416→ 208→ 104→ 52→ 26→ 13

のような反例が存在し,成立しない.つまり,「拡張コラッツ問題」は一般には成立しない.上記数列から偶数を除去すると,

13→ 33→ 83→ 13

これを5進で表示すると,23, 113, 313 となる.これらの数字に左シフト+1の操作を施すと,

231, 1131, 3131

のような5進数が得られる.これを10進表記すれば,66, 166, 416 だから,確かに循環している.これをどう解釈すればよいのだろう?少なくとも,k=5の場合には,(2)式のようなものは成立していないことは確かだろう.そもそもk>3の場合に,「長子木」のようなものが構成可能であるかどうかは,まったく自明ではない.というか,おそらく,k>3の場合には「長子木」のようなものは存在していないのではないだろうか?逆に言えば,長子木のようなものが構成可能であるのは,k=3の場合に限定されるのではないだろうか?いずれにしても,「長子木とは何か?」ということが追求されなくてはならないだろう.

Wikiには「コラッツ予想の他の形式」として,「ボトムアップ方式」という提案が記載されている.「ボトムアップ方式」というのは,いままさに我々がやっている方式そのもので,コラッツ写像の逆演算からボトムアップに木を構成しようとする試みである.この記事では21ステップまでのコラッツグラフのSVG画像↓が紹介されているが,この図版の数値は偶数を含むものであるため,かなり狭い領域しか表示できていない.「完全正則」という概念にも達していないため,証明にはまだほど遠い段階にあり,「予測」の域を脱していない.

image

拡張コラッツ問題で生成されるようなグラフを拡張コラッツ木ないし,k-コラッツ木と呼ぶことにしよう.上記で見たようにk-コラッツ木は必ずしも「木」にはならないが,そのトポロジーがどのようなものになるのかは興味がある.しかし,ここではこれ以上深入りしないで,先に進むことにする.さて,懸案の仮想木上の拡張アドレスコードに掛かることにしよう.本システムでは偶数は扱わないとしたので,非長子ノード用の書式を追加するだけだ.

区切り文字として「:」を追加する.完全木では区切り文字として,3倍数には「-」を割り当て,2倍数(偶数)を識別するために「*」を追加しているが,「:」と「-」は方式的にはかなり異なる.{x-y}は{x}-{y}であり,「-」で区切られた2つの枝番(XとYは親子関係)だが,{x:y}は全体で1個の一般木上の枝番(x:y)を示している(XとYは兄弟でXはコラッツ数列には表示されない).

B面ではMax branchesという値を表示している.拡張アドレスの場合はどういうことになるか?仮想木上にないノード(非長子ノード)の枝番は無視するしかないのではないか?というか,長兄ノードは仮想木上にあるので,その枝番を適用でよいと思う.というか,それしか方法がない.幸い非長子ノードはつねに終端に来るので問題ないだろう.⇒とりあえず,アドレス→番号変換は動作するようになった.

幹線木にも対応修正を入れておこう.幹線木をどう英訳するかでいろいろな候補が挙がっている.①Truncated tree, ②Stem tree,③Minimal regular tree などなど…Nを含む極小な正則木という意味では,③が一番正確であるような気もするのだが…Stem treeでも悪くないような気もする…幹線木というオプションが存在する意義自体がよくわからないという向きには,「極小正則木」という命名は説明になっているような気もする…⇒幹線木も動作している.あとは,コラッツ数列だけだ.

既存コードの出力は,仮想木上の経路だけで止まっている.たとえば,非長子ノードの19397では

4849    19397
227 (2)    3637 [2]
5 (3)    341 [3]
1 (1)    1 [4]

が出力される.左列が仮想木,右列が一般木だが,左列にはターゲットの19397は表示されていない.19397の仮想木上の経路は1. 3. 2:1で,アドレス変換では

1 [1]
5 [3]
227 (2:1)
19397

のように表示される.これを天地逆転したものが表示されるようにしなくてはならない.つまり,

19397
227 (2:1)
5 [3]
1 [1]

のようにならなくてはならない.⇒大体動作するようになった.

▲幹線木でアドレスに1. 2. 2. 2:1を設定すると,206277の代わりに825109という数字が出てくる.この数字は少なくともダンプ出力の中には見当たらない.successorという変数におかしな値が入ってくる.ループの中で余分なことをやっているようだ.幹線木ではDumpSuccessorsで実行している処理をループ中で重複実行していた.

隣接リストの内容をチェックしておこう.⇒アドレス変換はよいが,他の2つは間違っている.