非循環的有向グラフをハッセ図に転換する

軸線図グラフの動作はほぼ仕上がっているように思われるが,軸線が通らないサンプルが散見される.たとえば,STAGE6.ZELを#6 undosysで開くと下図が出力される.

image

これなどは,まだ最長鎖が取れていないようにも見えるのでチェックしてみよう.⇒どうも,やり損なっているようだ.カードに表示されている絶対世代番号で見ると,undosysは5,それと同世代のtopologyには2が割り当てられている.この番号は最長鎖検定ではなく,ハッセ図検定で付番しているものだが,こちらの方が正しい.いや,この図面は間違っていない.確かにtopology系の方が子孫系としては長いが,基準ノードのundosysから見ると傍系だ.現時点ではこのような図面になってしまうのは避け難いと思う.treeviewでソートした場合も同じだ.

image

この他,linktable, paegsetup, Bobjectでソートした場合にも同種の図面なる.つまり,血統軸線図の「軸線」とハッセ図の最長鎖は必ずしも一致しない.この2つをどのように調和させるか?あるいは,果たしてそれは可能か?ということを調べるためには,ハッセ図検定の中に踏み込むしかない.つまり,血統軸線図の場合には最長鎖検定を2度行う必要がある,ということだろう.既存ハッセ図検定と最長鎖検定をどうつなぐかというところはいまのところ不明だが,ともかく,一般のADGの最長鎖を求める検定の実装に入ることにしよう.その前に一つ最新のリリース版を起こしておくことにしよう.

現行のGetLongestChain(最長鎖の取得)を修正して一般のPDGを扱えるようにした.これまでは基準ノードの直系血族しか扱えなかったが,非連結な場合を含めてすべてのPDGを扱えるようになった.つまり,全系図を対象とする検定が行えるようになった.これをどこに使うかというと,重婚クラスタ検定で循環のないクラスタ図を生成したあと,このグラフから絶対世代番号を割り出すために使う.しかし,そこに組み込む前に本当に全系図のハッセ図を出力できるのかどうか試してみよう.それができれば,クラスタ図などはそれよりずっと小さいグラフなので容易に操作できることは確実だ.その前にまず,STAGE6.ZEL(30点)の枝グラフを生成し,それをエクスポートしたものを見てみよう.

image

オリジナルのZELファイルとまったく同じ系図が出力されている.全体図グラフには親子関係しか与えていないのだが,完全に同じものが生成されている.全体図グラフの枝は親子関係だが,父子関係,母子関係はそれぞれまったく独立に与えられているのに,一つの結婚としてまとめ上げられている.いや,このサンプルには単身婚しか含まれていないようなので別のサンプルを見ないと分からないが… まぁ,このまま続けよう.少なくとも兄弟は一つのボックスに入っている.最初にこのグラフをGetLongestChainに通してみよう.⇒GetLongestChainでエラーが出た.ノード数のカウントが合わないというエラーだ.

エラーはDeleteIsolatedNodeの中で起きている.いや,それよりずっと前,GetLongestChainの「基準ノードからのパスがない節点を削除する」で起きている.これはSIMPLEGRAPHの関数を使っていないためだ.⇒NODELIST::RemoveからSIMPLEGRAPH::Releaseに変えた.⇒これでエラーは解消したが,ファイルをインポートしようとして,「ファイルのオープンに失敗しました」のエラーになる.⇒最初のテーブルの列名の行しか入っていない.

「ループフリーグラフで自己ループ枝の登録は不可」というエラーが出ていた.今回はtempgraphという作業用グラフを使っているが,このグラフの属性は何も設定されていない.軸線グラフと同じ設定(SELFLOOPGRAPH | DIRECTEDGRAPH)にする必要がある.このグラフは普段は「系列枠リストのソート用」に使われている.しかし,実際に使われている形跡がない.最近の修正で大量廃棄されたコードの中で使われていたのかもしれない… ⇒インポートできるようになったが,カードが3枚しか残っていない.

image

これは先祖ノードと基準ノード(今回はundosys),末裔ノードだろう.⇒【2.3】基準ノードからのパスがない節点を削除するのところで4点削除されている.namesort,linktable,topology,NLISTだ.これらのノードを削除するのは間違いだが,それだけですべての枝が削除される結果になるだろうか?実際に枝が削除されるのは【4】距離が1以上の推移枝を削除するだ.ここで枝数30が一挙に3になってしまう.

「基準ノードからのパスがない(端点にマークのない)節点を削除する」とき,値が0ではNOMARKであることをチェックするようにして,上記4点の削除は止まった.推移枝の削除では枝数は40から27まで減少する.13枝削除されているが,まだ大多数は健在だ.次のRemoveDeadEndBranchで一挙にループ枝以外のすべての枝が切り落とされている.RemoveDeadEndBranchの論理が悪いのか,我々のアルゴリズムに根本的な誤りがあるかのいずれかだ.

一覧表のインポートで白紙カードが発生

一覧表ファイルのインポートで白紙カードが発生してしまうという事象が起きている.原因はインポートに先立って新規ファイルをオープンしているためだ.コード上ではこのカードは直ちに削除されているが,削除カウントゼロの状態で実行されているため,実際には効いていない.カウントを立てて削除を実行すると,今度は基準ノードが空というエラーが発生してしまう.新規ファイルをオープンするとき,同時に新規カードを自動的に生成するというのは仕様であり,変更することはできない.ただし,部分図ではカードゼロという状態が認められている.部分図は既存カード(の一部)を登録するものなので,勝手に新規カードを作るという訳にはいかないからだ.

インポートの手順はDLLとVBの間で1レコードずつ受け渡しするというプロトコルになっており,まず,最初に「インポート開始」,最後に「インポート完了」という通知を送ることになっているので,DLL側では現在インポート中ということを認識することは可能だから,なんらかの対策を講じることはできるだろう.ただし,現行では開始通知が送られるのは新規ファイルをオープンしたあとになっているので,この時期を早める必要がある.現行では,インポート中であることを示すフラグを持っていない.LINKTABLE::ImportStartではImportFileNameにファイル名を格納しているだけだ.

部分図と同じ扱いにしたいので,CHARTTYPE:図面種別にDISP_IMPORTという定数を新設することにした.インポート完了時には,元の図面種別に戻すのではなく,全体図に切り替えるのがよいのではないか?図面種別というのは(実行時に切り替えることは可能だが)ファイルに固有の値であり,インポートファイルはその情報を持っていないのだから,全体図表示が正しいと思う.現行ではインポートしても図面種別は切り替わらないという動作になっている.

このバグはかなりクセが悪い.ようやく押さえ込んだと思ったが,こんどは参照番号2という新規カードが作られている.⇒FAMILYTREE:DeleteCardDataではカード削除でカード数ゼロとなった場合には自動的に新規カードを作るようになっている.⇒ようやく収まった.以下の修正が必要になった.

  1. FAMILYTREE::ChangeKindredでCHARTTYPEとkparm.CHARTTYPEを設定
  2. インポート時,FAMILYTREE::DeleteCardDataでカードゼロのとき新規カードを生成しない
  3. LINKTABLE::ImportStartでChangeKindred(DISP_IMPORT)を実行
  4. LINKTABLE::ImportEndでChangeKindred(DISP_ALLCARD)を実行
  5. LINKTABLE::ImportEndでテーブル先頭カードの参照番号を返す論理を変更し,PDB->lookupを見るようにした
  6. 外部関数UpdateCardBaseでインポート中は基準ノード空で停止しない
  7. FAMILYTREE::RestoreBaseCardでインポート中ないし部分図モードのときは,BaseLink空で停止しない
  8. COUPLING::OpenFamilyBaseでInitKeizuParmを実行しない→暫定
  9. OpenFamilyBaseの出口でインポート中はSetKeizuParmを実行しない→暫定
  10. COUPLING::InitializeHeaderでkparm.initializeを実行しない→暫定
  11. VBのImportTableFuncで,①SetKindred(DISP_IMPORT)を実行,②Z.mDeleteCardを実行する前にZ.DeleteTableとZ.DeleteCountを設定,③SetKindred(DISP_ALLCARD)を実行

上記修正のうち,暫定の付いている8, 9, 10の3修正はそれぞれ,InitKeizuParm,SetKeizuParm,kparm.initializeで対処するように変更する.項目1で新規カードを削除しているが,最初からカードを発行しない方がよい.また,LINKTABLE::ImportEndでデータ不整合があるときのパネルも出さないようにした.新設したGetLongestChainはほとんど仕上がっていると思っていたが,まだまだだ.その後の処理も悪い.GetLongestChainでは出口で各節点に割り当てられている「距離」を「世代番号」に変換して戻るようにした.世代番号は0から始まる通番だ.また,戻り値を先祖ノードから最長鎖の長さに変えた.

MakeAntiChainListには引数で先祖ノードと末裔ノードを渡していたが,これらを廃止して,GetLongestChainが返す最長鎖の長さを渡すようにした.MakeAntiChainListの戻り値はwidthでこれは反鎖リストの最大サイズを返している.つまり,GetLongestChainは与えられたADGの高さを,MakeAntiChainListはADGの幅を返すものになっている.⇒FindJikusenAncestorでは,MakeAntiChainListのループを抜けた後の処理を省いて直ちに復帰するようにした.FindJikusenAncestorの論理はもう少し肉付けしなくてはならないが,当面はこれだけで間に合う.これで動作は大分まともになったが,まだエラーが出ている.

AFTERCUTEDGE.ZELを血統軸線図で開いて,TOPOLOGY:FindJikusenAncestor→ DeleteIsolatedNodeで節点を削除しているとき,CleanSansyo→ TakeRemainSansyoでリスト末尾不一致エラーが発生する.⇒deleteで削除していたところを.NODELIST:deleteElementを使うようにして解決した.

BEFORESTAGE6.ZELを開いて,MakeAntiChainListが幅0を返してくる.GetLongestChainの戻り値もゼロだ.このサンプルはカード数9だが,基準カードの#1が孤立ノードになっている.孤立ノードは除去されるようになっているが,基準ノードだけは残すべきではないか?⇒GetLongestChainの引数の基準ノードが空になっている.⇒確かに現行論理ではそのようになるだろう.現行ではノードを直接登録するのではなく,枝を登録するときに付随して登録されるようになっている.つまり,枝を持たないノード,言い換えると親も子もいないノードは登録されない.FindJikusenAncestorの冒頭で事前登録するようにした.

どうも,jikusenというプロパティがどこかで落とされてしまっているようなのだが,なぜだろう?ソースコードの大規模修正が入って,砲撃を受けた市街地のような様相になっている.一度整理してから進めることにしよう.オプションは以下の2つだけだが,広大な領域が,#if 0で止めてある.これを整理すると大分見易くなるだろう.

  1. GetLongestChainを使う@20210205 1箇所
  2. インポートではCHARTYPEをDISP_IMPORTに切り替える@20210206 17箇所

Jikusenzu.cppだけで687行削除した.⇒細かい修正がかなり必要になったが,軸線グラフの最長鎖を求める部分に関してはほぼ完全と言える状態になった.ただし,軸線図はまだ正しく描画できていない.基準ノードと先祖ノード,末裔ノードを(暫定)確定できるようになったので,次の段階に進もう.下図で見るようにCARDLINKのレベルでは完全に軸線が通っているのだが,図面上の具体的なオブジェクトであるNAMEBOXとMARGBOXへの設定に手抜かりがあるように思われる.

image

どこかで軸線グラフの枝を完全に失っている.⇒系統並び替えのSETRELATIVEフェーズでRemoveDeadEndBranch「行き止まりノードの枝をグラフから削除」をやっていた.従来論理では先祖ノードと末裔ノードにロックが掛かっていたので,それでよかったのだが,現行では反鎖リストの生成に掛かる前にロックを解除しているため,すべての枝が削除されてしまう.このブロックは全面的に廃止でよい.

image

ここまで来れば軸線図はほぼ仕上がったも同然ではないだろうか?

ADGの最長鎖を求める汎用関数

Version 2.2.0.023 Release 2021-02-05を所内リリースした.これでSTOPで停止することもなくなるだろう.まず,ADGの最長鎖を求める汎用関数を作ってみることにする.これがあれば,軸線図でもクラスタ図でも使えるようになる.関数名はSIMPLEGRAPH::GetLongestChainとし,TOPOLOGY::FindJikusenAncestorから既存論理をコピーするところから始める.⇒実装した.SIMPLEGRAPH::GetLongestChainでは昨日書いた手順書のステップ9で一旦復帰することにした.復帰時のグラフは複数のsourceと複数のsinkを結ぶ複数の最長鎖が残った状態となっているはずだ.アプリでは,必要があれば,長子優先/父系・母系などのオプションによる選択をここで実施することができる.

GetLongestChainをFindJikusenAncestorに組み込んで動作を見てみよう.実装完了した.一応動くようになった.下図は,ZTシステム構成図7.ZEL 直系血族図 基準ノード=#30 MDBの軸線グラフをGetLongestChainで処理してエクスポートしたものだ.

image

先祖ノードはcouplingとCOUPLINGの2点,末裔ノードはownerからNODULEまでの13点が残っている.その他,多重パスが2個所に出現している.familytreeからtopology, namesort,linktableの3点に分岐,配列要素からmargbox, noduleの2点に分岐している.アプリ側ではこのグラフから適切な先祖ノードと末裔ノードを選択し,重複パスを解消して単線の軸線グラフを取り出すことができる.ただし,現行論理ではその辺りで失敗しているので,この系図の図面はまだ出力されていない.最長鎖の長さを知りたいだけなら,すでに結論は出ている.ノードMDBを通る最長鎖の長さはノード数をカウントして8だ.(距離というときはパス上の枝数を言うので7ということになる)

この分岐をどう始末するか?が問題だ.既存論理はできが悪いので全面破棄することとし,一から書き直すことにしよう.軸線グラフが単線になっているかどうかをチェックするのは簡単だ.枝数と節点数を比較し,枝数+1=節点数となっていれば,軸線を得られたことになる(グラフが連結で循環を持たないという前提の下で).ダブっている節点を任意に削除してこの状態に持ち込むことが可能だが,一つのノードを削除するとトポロジーが変化してしまうのでそれをコントロールする必要がある.まず,SIMPLEGRAPHが持っている連結成分リストを使って,反鎖リストを作るところから始めよう.反鎖リストというのは世代別にノードを集めたリストだ.⇒できた!

このサンプルでダブっているのは4箇所だから,4段階で処理できる.とりあえず,半鎖リストの先頭ノードを残すという論理を組んでみよう.⇒実装したが,枝数10,節点数8でループしている.なにか余分な枝が残ってしまっているようだ.自己ループが2つ残っている.GetLongestChainの出口で外しておくことにしよう.いや,ダメだ.このロックを外すと反鎖リストの始末も付けられない.自己ループは残しておかないと動作に支障をきたすので,最終的には枝数 – 1=節点数となるはずだ.⇒namesort→MDBという多重枝がある.なぜだろう?軸線グラフでは多重枝は認められていないはずなのだが… ノードの短絡や縮約を実行すると多重枝が発生する可能性はあるが…

どこで多重枝が発生しているのか?調べてみよう.GetLongestChainの入口ですでに多重枝が発生している.つまり,チェックがまるきり効いていない.⇒SIMPLEGRAPH::addではまったく検査していない.大文字のSIMPLEGRAPH::Addでは検査している.いや,addはAddを呼び出しているだけだ.使っている関数が間違っていた.findedgeではなく,FindEdgeだ.⇒ループを抜けてきたが,まだ悪い.

軸線は通ったが,孤立カードが13枚も残っている.しかも,そのうちの一つ#1は空白カードだ.⇒枝数 – 1=節点数でブレークしているためだ.節点数=20 枝数=21でブレークしてしまう.やはり,最長鎖の長さを見ないとダメだ(孤立カードが残っている,つまり連結していない).枝数 – 1=最長鎖の長さでブレークするようにしてほぼ片付いたが,空白カード1枚は残っている.なぜだろう?いや,これは残っているのではなく,ZTの多重カードのようだ.

image

いや,違う.白紙カードが別カードとして入っている.一覧表で見ても9枚になっている.しかし,グラフのダンプでは節点数=8 枝数=9となっているので,エクスポートでなにか余分なものを出力しているのではないか?⇒テキストエディタでCSVファイルを開いてチェックしてみたが,レコードは8件しか登録されていない.エクスポートする直前に自己ループ枝を除去しても変化しない.インポート側を見るしかない.インポートでも8枚しか処理していない.

LINKTABLE::ImportEndでMakeMarriagePageというのを実行している.ここで新規カードが発生している模様だ.いや,これは違う.結婚リンクを生成しているところだ.ImportTableFuncでは最初に新規ファイルを開いているが,このときすでに無名のカードが作られている.

系図軸線の長さをnとするとき,系図はn個の世代に分割される

hishiさんから問い合わせが入っているので返信した.

duke.hishi さま

お問い合わせありがとうございます.
ARKGWONというライセンスコードは存在しません.
バージョン番号から多分下記に該当するのではないかと思います.

ライセンスコード:ARKGWQN
ライセンスキー:JSRQWZD

お試しください.(ライセンスキーをコピペしてください)

馬場研究所
馬場 英治

PS: hishiさんから最初のお問い合わせを頂いたのは,2004年1月頃のことと思われますが,それからあっと言う間に17年もの年月が経過してしまいました.現在,「正式版」の年内リリースに向けて鋭意開発を進めているところですが,これまでどうしても突破できなかった壁を次々と打ち破る怒涛の進撃が続いています.ゼルコバの木ユーザ会のアルファ会員,ベータ会員の皆様には正式版初版を無償提供することをお約束していますが,hishiさんは加入年度から見てアルファ会員であることは間違いありませんので当然その資格をお持ちでいらっしゃいます.これからも末永くご支援賜りますようお願い申し上げます.

閉路(循環)を持たない無向グラフを木というように,有向閉路を持たない有向グラフはADG(Acyclic Directed Graph, 非循環的有向グラフ)と呼ばれる.ADGのグラフ表現の中で特に矢線の向きが定方向(通常は上向き)であるようなものをハッセ図という.ZTでは系図図面上に配置されるカードの絶対世代番号を決めるために作成される重婚クラスタグラフをハッセ図と呼んでいるが,クラスタ図(重婚クラスタグラフ)は定義上ハッセ図そのものであるので,今後とも適宜使い分けることにしよう.クラスタ図を描画するための方法は色々考えられるが,もっとも確実な方法は,まずADGの最長鎖を求め,それを基準として各ノードの「世代」を決定することであると考えられる.最長鎖に関しては以下の定理が知られている.

定理:(P, ≦)を半順序集合,Pに含まれる最長鎖の長さをnとするとき,Pの要素はn個の互いに素な反鎖に分割される

ここで反鎖(antichain)とは半順序集合Pの部分集合でそのどの要素も互いに関係を持たないようなものを言う.ゼルコバの木の用語で言うと,この「反鎖」の実体は「世代」に他ならない.平たく言えば,同一世代のノード同士には親子関係は存在しないという意味であり,別に難しいことを言っている訳ではない.反鎖への分割の仕方は必ずしも一意ではないが,その個数はつねにnになるというのが定理の趣旨だ.

「最長鎖」という概念は「軸線」を構成するために持ち込まれたものだが,最長鎖(軸線)が得られると軸線の長さをnとして系図上のすべてのノードはn世代に分割されると言うことができる.つまり,最長鎖を求めることは「クラスタ図」を得るためにも必須である.従って,最長鎖を求めるアルゴリズムを確立することが最重要な課題となってくるが,我々はすでにそれを得ているのではないか?そのことをまず,確認しておきたい.現行では下記のような手順で軸線を決定している.

  1. 基準ノードAの直系血族図に属するノードを節点とし,ノード間の親子関係を枝とする有向グラフGを生成する
  2. 基準ノードAの距離A(d)=0とし,Aに接続するノードでAより上にあるもの(Aの親)に-1,Aより下にあるもの(Aの子)に+1を与える
  3. 距離dの値を持っているすべての節点Nにつき,Nが負値を持っている場合には,Nと接続するNの親PにN(d)-1を与える,Nの親Pの距離P(d)≦N(d)-1のときには上書きしない.また,Nが正値を持っている場合にはNと接続するNの子KにN(d)+1の値を与える ただし,Nの子Kの距離K(d)≧N(d)+1の場合は上書きしない
  4. これをグラフGのすべての枝の始点と終点の距離dが確定するまで反復する (始点の距離dが更新された場合には終点の値を更新する,値が負値の場合はその逆)
  5. グラフGの節点のうち,入次数0でかつ最小のd値を持つものを先祖ノードとする(同じ値のd値を持つものが複数ある場合は任意選択する)
  6. グラフGの節点のうち,出次数0でかつ最大のd値を持つものを末裔ノードとする(同じ値のd値を持つものが複数ある場合は任意選択する)
  7. 先祖ノードと末裔ノードには自己ループ枝を追加する
  8. グラフGのすべての枝を点検して端点の距離dとd’の差の絶対値が1より大きいものがあればこれをグラフから取り除く
  9. 枝の除去によって入次数ないし出次数0となった節点はグラフから取り除く(先祖ノードと末裔ノードにはループ枝が追加されているので入次数ないし出次数0にはならない)
  10. 節点の除去によって始点ないし終点を失った枝はグラフから取り除く
  11. ステップ9に戻って静定するまで反復する
  12. 入次数ないし出次数が2以上の節点に接続する枝をグラフから取り除く
  13. ステップ9に戻って静定するまで反復する
  14. グラフに残った節点は(先祖ノードと末裔ノードを除き)すべて入次数および出次数1で先祖ノードから末裔ノードに至るチェーンを構成する

この手順によってつねに基準ノードを通る最長鎖を得られることが証明できるだろうか?基準ノードを必ず通る路が得られるということに関しては問題ないだろう.スタート地点が基準ノードでそれから上下に分かれているので,基準ノードは明らかに「関節点」になっており,このノードが削除されることはあり得ない.ステップ3, 4の距離数の割当てから,基準ノードの上の枝も下の枝もつねに,

始点の距離数<終点の距離数

が成立することは明らかである.つまり,「番号」の逆転はあり得ない.ステップ8で除去される「端点の距離dとd’の差の絶対値が1より大きい」ような枝を推移枝と呼ぶ.下図では推移枝の始点の距離dは1,終点の距離dは3なのでその差は2となり,除去の対象となる

image

明らかにこのような推移枝がすべて除去された後のグラフGの枝はすべて始点と終点の距離数の差は1のものだけになるから,パス上の節点はすべて通し番号が振られたような状態になっていることは確実である.また,①→②,②→③のような枝は(ステップ8までは)除去されることはないので,このグラフが非連結にならないことも確認できる.ステップ9, 10を実行するとグラフには先祖ノード(source)から出て末裔ノード(sink)に入る枝しか残らないことになるから,source→sinkの複数のパスだけが残った状態になる.これらのパスはすべて通し番号でどの一つを取っても最長鎖になっていることは明らかであるから,任意のパスをカットして単線化すれば求めるチェーンが得られる.

これで証明になっているだろうか?ただし,一般の場合には,最長鎖が基準ノードを通るという保証はない.軸線図の場合には最長鎖つまり「軸線」が基準ノードを通っていることは必須だが,クラスタ図の場合,最長鎖が基準ノードを通っていないという場合もあり得ると考えられる.従って,クラスタ図で最長鎖を求める場合には上記手順をそのまま適用することはできない(対象も基準ノードの直系血族図ではなく系図全体が対象となる).この場合は,系図上の先祖ノードの各々について,上記手順(基準ノードを先祖ノードと読み替える)で最長鎖を求め,その中で最長のものを系図全体の最長鎖とするしかないような気がする.これよりもっと効率的な方法はあるだろうか?既存の「ハッセ図検定」でどんなことをやっているのか見てみることにしよう.

先に進む前に最近の修正をフィックスしておくことにする.仕掛りのオプションとしては以下がある.

  1. adjustGenerationRangeを停止する@20210117 1箇所 → 廃止
  2. 無向グラフでは並行枝を登録しない@20210128 2箇所
  3. 直系血族配偶者は切り離す@20210129 → 廃止
  4. FindJikusenAncestorで基準ノードの自己ループ枝を登録しない@20210201 2箇所 → 廃止
  5. FindJikusenAncestorで軸線ノードを決定する@20210201 8箇所
  6. 軸線ノードの抽出・TYW・ZTYWを禁止する@20210202 2箇所

ZTシステム構成図7.ZELの直系血族図 基準ノード=#1 couplingを開いて,MARGBOX::setGoodSonで停止した.(GetHeadSibling() != this)という理由だ.この関数は血統軸線図の軸線GoodSonを設定するためのもので,GetHeadSiblingは拡張子ども枠の代表枠を返す関数だ.代表子ども枠は通常先頭枠であり,必ずしもgoodsonを保持するとは限らないような気がするのだが… この関数はTOPOLOGY:BuildShaftLineの中から呼び出されている.

MARGBOX::setgoodsonではgoodsonはgetmyplaceに格納されるので,単体結婚枠がgoodsonを持つというのでよいのではないかと思うのだが… GoodSonは代表枠が保持することになっていたのだろうか?「@2016-09-29 GoodSonを上位枠が持っている場合がある」というコメントもあるので,代表枠が持つことになっている可能性はあるが… setgoodsonと矛盾するような気がするのだが… 確かにそのように取れるところもあるが,あまりよい仕様ではないような気がする.

障害が起きているのはMARGBOX #668:#776 tribelist(0)+→#1279 baselist基本世代枠(1)で,その前にMARGBOX #731:#776 tribelist(0)+→#1278 treeview(3)がある.⇒軸線枠はjikusenというプロパティを持っているのだから,あえてGoodSonという変数を使わなくてもよいような気がする.NAMEBOXにはGoodBoxという変数もある.これらは軸線の構成に手こずってやむを得ず導入されたものではないか?おそらくどちらも廃止できるのではないかと思われるが,ここでは停止しないでプリント文を出すだけとしておこう.

MARGBOX::CheckMarchainでもエラーが出た.「非代表枠のgoodson」というメッセージが出ている.確かに拡張枠の場合はgoodsonを代表枠に格納するという決まりになっているのだろう.あまりよいアイディアとは思われないが,これに合わせるしかないのではないか?⇒BuildShaftLineでsetGoodSonするときに代表枠があればそちらに設定するようにした.⇒エラーが2つとも解消したのでまぁ,これでよいことにしておこう.

半順序集合上の「最長鎖」を求める問題

ZTシステム構成図7.ZELの直系血族図 #1 couplingを軸線図法=1で開いて,NAMEBOX::MakeZeroTYWで停止する.MakeExtractBoxが空を返している.障害が起きているのはNAMEBOX #1272 UNDONODE(1)で,このノードもその人名リンクも軸線のマークを持っていない.⇒抽出の対象となっているのはこのノードではなく,このノードの右手枝にある結婚枠の中にある子どもノードonlysonだ.この子どもノードはMARGBOX::OnlyOneVisibleSonで取り出されている.⇒onlysonとその人名リンクが軸線である場合にはゼロ復帰するようにした.

image

これで基準ノード=#1 couplingの軸線図を出力することはできたが,明らかにこの軸線は短過ぎる.本来ならnodule→NODULEが図面の一番下に表示されなくてはならないところだが,noduleを多重表示することで「ごまかし」ている.現行の軸線決定アルゴリズムが誤っていることは明らかだ.軸線というのは,基準ノードの先祖ノードとその末裔ノードを結ぶ最長経路と定義される.親子関係(養親子関係を含む)は半順序関係であり,血統軸線図の中に埋め込まれた直系血族図という半順序集合上の「最長鎖」を求める問題として定式化できる.

これを一般化すれば,非循環的有向グラフの特定の2点を結ぶ最長経路を求める問題と言い換えることができる.これをさらに一般化すれば,有向グラフ上の任意の2点を結ぶ最長経路を求める問題となり,もし,この問題を解くことができればNP完全問題が解かれたことになると解される.あるグラフGの任意の2点間を結ぶ最長経路がグラフGのすべての点を含むものであれば,それはハミルトンパスであり,その2点を結ぶ有向枝があればハミルトン閉路になる.任意の2点間の最長距離を計算する多項式時間アルゴリズムが存在するとすれば,ハミルトン閉路問題はグラフGの節点数をNとして,高々そのO(N^2)倍で解けることになる.つまりNP=Pであることが証明されたということになる.

従って,「軸線ノード決定問題」,つまり,「半順序集合上の最長鎖を求める問題」がそんなに簡単に解ける訳がないと考えるのが順当だろう.現行アルゴリズムがどんなことをしているのか?どこで失敗しているのかを見ておくことにしよう.FindJikusenAncestorという関数だ.

  1. 基準ノードの自己ループ枝を登録する
  2. 直系血族図から親子関係を枝とするグラフを生成する
  3. 基準ノードに連結しているすべてのノードをマークする
  4. 基準ノードからのパスがない(端点にマークのない)枝を削除する
  5. 最長軸線の終端ノード(先祖ノードと末裔ノード)を決定する
  6. 最長軸線の終端ノード(先祖ノードと末裔ノード)をロックする
  7. 行き止まりノードの枝をグラフから削除する
  8. 枝が削除されている場合にはステップ3に戻る
  9. 余分な経路(重複パス)をカットして線形グラフに変形する
  10. 父系/母系優先を枝グラフに反映する
  11. 任意の重複枝を削除する
  12. 行き止まりノードの枝を削除してステップ11に戻る
  13. 基準ノードに連結する最長軸線を切り出す
  14. 行き止まりノードの枝をグラフから削除する
  15. 先祖ノードと末裔ノードを返す

ステップ8まではおそらく問題はないと思われる.おそらく一番クリティカルなステップは9の「重複パスをカットして線形グラフに変形」というところだろう.線形グラフというのが具体的にどのようなものを指しているのかはよく分からないが,イメージとしてはグラフを先祖ノードから末裔ノードまでをつなぐチェーンの集合のようなものに変えようとしているものと推定される.ステップ11でも「重複枝を削除」しているが,「重複枝」と呼んでいるのは必ずしも「多重枝」ではなく,単に「分岐」を意味しているようだ.

既存プログラムにはdumpgraphというグラフをテキスト形式でダンプするルーチンが整備され,デバッグ時には各ステップでそれを実行していたはずだが,現行のような描画システムを使って直接グラフを目で確認するという方法に勝るものではない.ステップ9の入口でインポートしたグラフを出力すると,孤立ノードを除いて,カードが30点残っている.

image

とりあえず,ここまでは問題ないと言えそうだ.しかし,ステップ8のループ出口でグラフを出力すると,ここではすでに軸線はpagesetupを通るパスで確定してしまっている.

image

上記では単に「マーク」と記しているが,実際に格納されるのは基準ノードからの距離Dであり,上りならばー,下りならば+が与えられる.一つのノードに到達するパスは複数あるので,下りならばより大きい値,上りならばより小さい値が設定される.ステップ9ではこの距離を基準に分岐を決めているのだが,成功していない.ステップ9の入口の軸線グラフにこの値を表示してみよう.

couplingは先祖ノードであると同時に基準ノードでもあるので,0,familytreeとpagesetupは1,topology, undosys, namesort, linktableは2という値を持っている.末裔ノードのNODULEは11, noduleは10で,確かに最長パスを経由した値が入っている.pagesetupとcouplingの距離は1だが,pagesetup→treeviewでは3の距離がある.つまり,treeviewが最長パス上にあるかないかは分からないが,少なくともpagesetup→treeviewの枝が最長パス上にないことは明らかだ.

現行アルゴリズムでは枝の両端点の距離Dを比較しているだけで,その絶対値が1より大きいかどうかということには無関心であるようだ.これは重要なポイントなのでまず,この論理を追加してみよう.枝の端点の距離Dの差が1より大きい枝を推移枝と呼んでおくことにしよう.推移枝は最長パス上にない枝と考えられるのでグラフから除去するという方策を立てて実装した.以下の枝が削除されている.

  1. #1:coupling(0) → #4:treeview(4)
  2. #5:pagesetup(1) → #4:treeview(4)
  3. #5:pagesetup(1) → #4:treeview(4)
  4. #7:topology(2) → #33:baselist基本世代枠(4)
  5. #130:EDGELIST(5) → #61:NLIST< LISTNODE, CID>(7)
  6. #33:baselist基本世代枠(4) → #61:NLIST< LISTNODE, CID>(7)
  7. #229:nlist(6) → #62:LIST(8)
  8. #118:UNDONODE(5) → #201:nodule(10)
  9. #156:配列要素(5) → #201:nodule(10)
  10. #235:Bobject(5) → #201:nodule(10)

この結果,軸線は下図のように変化した.

image

明らかにどこか切り過ぎている.pairlist→NLIST< LISTNODE, CID>という枝があるはずなのだが,どこかに消えてしまっている.上のカット枝のリストの中には入っていない.推移枝をカットした後の軸線グラフは下図のような状態になっている.

image

一番大きな原因はNLISTが親を失ってしまったという点だが,不審なのはNLISTの距離Dに61という大きな数字が表示されている点だ.その下の7という数字がむしろそれに該当するように思われるのだが… もしこの数字が7であれば,pairlistは6という数字を持っているので,pairlist→NLISTは存続できたのではないか?

いや,これはどうもインポートしている一覧表データの読み込み時の問題ではないかと思う.NLISTはNLIST< LISTNODE, CID>という名前を持っているが,「,」は区切り記号として使われているので,名前の中には使えない.⇒「,」を日本語文字の「,」に変えたら直った.直っただけでなく,チェーンが繋がった.

image

後は目を瞑っていても料理できる.処理後の軸線図を出してみよう.

image

!完璧だ!この図面「ZTシステム構成図7.ZEL」には,これ以外の軸線はあり得ない.…残念ながらその後が悪い.重婚クラスタ循環が存在しない図面で多重が3件も発生してしまっている.

image

クラスタ図(重婚クラスタグラフ)を出してみよう.

image

これは重婚クラスタグラフを軸線図で表示したものだが,軸線なしでも多分変わらないと思う.⇒実際,その通りだ.11世代ある.絶対世代番号はこの図面に従って出力しているはずだから,それに従って展開すれば,軸線を描画することは不可能ではないはずだ.

おそらく,クラスタ図作成の次段階である「ハッセ図の生成」というところでやり損なっているのだろう.実際のところ,クラスタ図がここまで出来ているのなら,「ハッセ図の生成」と呼ばれるプロセスはほとんど不要とさえ言える.ハッセ図の生成ステージは2段階の処理になっている.①TestInevitableMultiZeroと②VerticallyTightenHasseDiagramだ.もう一度洗い直しするしかないだろう.

軸線グラフをZTにエクスポートする

BuildShaftLineの既存論理を完全に廃止して,FindJikusenAncestorの事前検定で決定された軸線ノードのセットに準拠して軸線を構成するようにしたが,まだ一箇所だけ軸線の折れ曲がりが残っている.重婚クラスタ図をエクスポートできるようになったので,デバッグ時に別アプリでこのファイルをインポートして参照できるようにしておきたい.最近のバージョンでは一覧表のインポート・エクスポートが実行不能になっていたので,新しい版をリリースする必要がある.Version 2.2.0.022 Release 2021-02-02を暫定リリースすることにした.

この版をインストールしてデスクトップから実行すると,下図のように完全に軸線の通った図面が出力された.

image

非常に喜ばしい結果だが,同時に大きな問題でもある.デバッグモードとリリースモードの動作が異なるというのは突き止めることが困難な「バグ」とみなされる.デバッグ版,リリース版ともに世代数は9段で同じ.異なるところは,デバッグ版ではbaselistが多重カードになって2箇所に出現しているという点だ.baselistには親が2人いる.topologyとtribelistだ.tribelistはtopologyの子どもなのでデバッグ版ではbaselistと同じ箱に入っているが,リリース版ではtopologyの子ども枠に入っているbaselistはLDRになってtopology→LDR→baselistのように接続されている.この動作上の差異を突き止めなくてはならない.

一般にこのような場合には,デバッグ用のコードが実動作に影響している可能性がもっとも高いと考えられるが,関係するコードを見つけるのはかなり難しい.表示されているbaselistの仮ノードは2つしかない.(0)がtopologyの子ども枠の中にあるノードで,(1)がtribelistの直下にあるものだ.baselist(0)がデバッグモードでLDRになれない原因を調べてみよう.⇒NAMEBOX::EraseGhostNameでUnErasableがERR_UNERASABLEを返している.⇒UnErasableでは軸線ノードは消去不可としているためだ.これは結局軸線ノードのマーキングが間違っているためと考えられるが,リリース版ではなぜそれが起きないのか?

開発環境ではリリースモードとデバッグモードの動作に差異はない.つまり,リリースモードでもbaselistが多重カードになるという動作だ.⇒インストールしたつもりだったが,バージョンが変化していない.アンインストール→再インストールでリリース版でも同じ状況になることが確認できた.つまり,軸線が通る図面を出力していたのは,一つ前のバージョンだったということになる.そこまで戻るというのもあまり意味がないので,このままシューティングを進めよう.

baselistの2つの仮ノードがどちらも軸線のマークを持っているというのは,マーキングの論理の不整備だ.⇒そのノードの人名リンクが軸線であるというだけでなく,軸線グラフ上の枝をチェックして結婚枠の親ノードが軸線ノードであることを確認するようにしてこの問題は解決したが,その後の動作が悪い.TRIBELIST::SortTribeListでCheckTribeListのエラーが発生し,その後もエラーが連続発生して続行不能になる.

「TYW仮ノードは消去された」というダンプが出ているので,baselist(0)がTYW婚に組み込まれたことは間違いない.軸線ノードは抽出枠やTYW婚に転換できないという論理はすでに組み込まれているが,これを「軸線人名リンクから派生するすべての人名ノードは抽出・TYW転換禁止」とする以外なさそうだ.軸線を構成するための論理は硬質で融通が利かないので,フロート婚TYWの自由度とは相容れないということだろう.これでZTモジュール構成図 TEST2.ZELの問題は解決した.

image

ZTシステム構成図7.ZELを#1 couplingで開いて,PAIRBOX:MoveChannelToで停止した.(!movesingle && IsBundled())という理由だ.movesingleがONなら単体ノード対の移動,OFFなら端点共有束全体を移動する.端点共有束移動の場合は,引数のノード対は共有束の代表ノードでなくてはならないという条件だ.⇒呼び出し側のRepairPairBoxを修正して,単体ノード対の移動をサポートした.

上記サンプルを血統軸線図で開いて,MARGBOX::MoveExpandBoxで停止した.「結婚枠世代不一致」が発生している.障害ノードはMARGBOX #732:#823 描画Yリスト(0)+→.この結婚枠は軸線枠ではない.しかし,軸線図を設定しているので,影響している可能性はある.TRIBELIST::GoDownStreamで最初にMARGBOX::DecideRoleで結婚枠のownerを設定した時点では一致している.

人名枠側の世代が2世代分下に下がっている(+2している).つまり,世代関係が逆転してしまっている.このノードの世代番号は4→3→6のように変化している.⇒CARDLINK::DownStreamで「軸線枠は軸線ノードの下で展開される@20210201」とする修正にミスがあった.すでに展開済みの結婚を展開する動作になっていた.⇒これを修正してもまだ,エラーが残っている.軸線なしでは発生していないので,軸線に関わるものと思われる.軸線グラフを出してみよう.

image

このサンプルの基準ノードはcouplingで軸線は,

coupling[0]→… pagesetup[6]→ treeview[7]→ Bobject[8]→… nodule[10]→NODULE[0]

の6ノードだが,世代的にはcouplingの世代0からpagesetupの世代6までかなりの開きがある.Bobject[8]とnodule[10]の間も1世代空いている.このような状態でうまく軸線を引くことができるのだろうか?かなり難しいような気がする.重婚クラスタ図を出力してみよう.

image

これで見ると世代が分離しない軸線が存在するかのように見えるが,クラスタ的には繋がっていても,内部のノードがそれに対応しているという訳ではない.しかし,軸線グラフで取り出された軸線が本当に最長軸線になっているかどうか?という点に関してはかなりの疑問がある.

coupling→pagesetupとなっているが,familytreeを通るパスの方が長いのではないか?クラスタ図の方はおそらく間違いの入る余地はないと思われるが,「ハッセ図検定の後でこのグラフを取り出すと上の方がちょん切れたようなものになっている.ハッセ図検定のロジックの検証はまだ取り掛かっていない部分だが,少なくともハッセ図検定の出口ではそれに対応したクラスタ図が出力できるようになっていなくてはならないと思う.どちらも荷の重い課題になりそうだ.

先に軸線グラフの正当性を検証することにしよう.これは軸線図の方が簡単だという訳ではないのだが,少なくも出力は単純なチェーンなのでわかり易いShir.FindJikusenAncestorのステージ【1】の軸線グラフを見た限りでは,coupling→ familytree→ topology→ graph1→ complist→…→DATALIST→ nodule→ NODULEというパスが一番長いように思われる.このパス上のノードは11個でpagesetupのパスより5つも多い.このパスが選ばれなかったのはなぜだろう?

一覧表のインポート・エクスポート機能が壊れてしまった

ファイル:一覧表のインポート,エクスポートが機能しなくなっている.仕掛りのバージョンでエクスポートしたCSVファイルをインポートしても新規ファイルの状態になるか,白紙のカードが横に並ぶだけで完全に壊れてしまっている.古いCSVファイルを開いても同じ状態だ.ドックに入っている間に「錆びついてしまった」という感じで,原因はまったく不明.2020/11/10の「再開発スタート版」では,2012/03/08に保存された「タモリさんちの超複雑な家系.CSV」を開くことができる.(警告パネルが出るがこれは動作とは無関係)このバージョンでは自分でエクスポートしたファイルをインポートすることもできる.

一覧表のインポート・エクスポートは主にVB側の担当なのでDLLはほとんど関係していない.VB側のコードは再開発スタート後もほとんどいじっていないはずであり,ましてインポート・エクスポート周りなどまったく触っていないはずなのだが… ⇒WinMergeでVBのソースファイルを比較してみたが,ざっと見た限りでは有意の差は見出せなかった.ともかく現行版をデバッグしてみることにしよう.現行版でエクスポートしたとき,一行目のフィールド名がすべて空になっている.この点をまず見ておこう.フィールド名は以下のコードで取り出されている.

RStr = RStr & .Columns(ColumnIndex).HeaderText()

.Columns(j).HeaderText()でフィールド名が取り出せない.⇒一覧表が表示されていないためだ.一覧表エクスポートのオプションでは「現在表示されている列だけをエクスポートする」という選択肢があるので,その場合には表示されなくてはならないかもしれないが,「すべての情報をエクスポートする」でエクスポートできないというのは不都合だ.アプリ起動時,MDIForm_Loadでは前回開いていたウィンドウを同じ位置にオープンしているが,その前に一覧画面とカード画面をロードしておくべきではないか?

いや,MDIForm_LoadでCardTable.InitTableを実行している.これで十分なはずだ.いや,InitTableは実行されているが設定が消えている.⇒設定が消えているのではない.アクセスが間違っている.コードではHeaderText()でフィールド名を取り出すようになっているが,実際に使われているのはNameというプロパティだ.古いバージョンを見てもそうなっているので,このコードは間違いではない.むしろVSで仕様変更しているのだろう.

HeaderText()をすべてNameに書き換えて,エクスポートの一行目は正しく出力されるようになったが,まだインポートはできない.しかし,古いバージョンではインポートできているので,仕掛り版のインポートが悪いということになる.ImportTableFuncでindices()の中身がすべて-1になっている.indices()には以下の式で値が与えられている.

indices(i) = CardTable.HeaderList.Items.IndexOf(value(i))

long __stdcall GetRecordでマクロIFEXPORTSACTIVEによってゼロ復帰していた.このマクロはすべての外部アクセス関数入口に設置されているもので,FAMILYTREE未構築ないしフェーズがINITIALSTATE以下のときはゼロ復帰するようになっている.このマクロを外して動作するようになった.早速藤壷の宮の重婚クラスタ図を開いてみよう.

image

 
※6

 

先帝,先帝の后宮,源氏宮

 

桐壷院,藤壷の宮

 

光源氏
 

 
冷泉院,弘徽殿女御,秋好中宮,王女御,左大臣の女御,中納言の娘,宰相の娘,冷泉院の御息所

実際には,この下に,弘徽殿女御→女一宮 (冷泉院の)と冷泉院の御息所→{女二宮 (冷泉院の),若宮 (冷泉院の)}が続いて,6世代図になるのだが,最下段のカードはいずれも独身で子どもがいないため,クラスタ図からは省かれている.薫のZ木家系図のクラスタ図を取ってみよう.原図はこんな感じだ.

image

クラスタ図を出してみよう.

image

一院の下のボックスにクラスタが2つ入っているというのはやや奇妙だが,これは摂政太政大臣の属するクラスタと桐壷院のクラスタのメンバー間には繋がりがないということを意味している.一院のクラスタからはそのどちらのクラスタにも親子関係で接続するため,一つの結婚枠の中にクラスタが2つという状態になっている.これはもちろんクラスタ循環と呼ばれるものではない.用語的に言うとこのクラスタ図は連結なので,「重婚クラスタ属」と呼ばれるものに該当する.

クラスタ図のもっとも大きな特徴は,メンバーカードがいずれか一つのクラスタにしか所属していないという点だ.これが系図木や婚姻木などとのもっとも大きな相違点であり,これによって各カードに「絶対世代番号」を与えることができる.さらに,XBTWがサポートされれば世代差のある結婚による多重を解消することができるので,多重カードゼロの系図,つまりパーフェクトな系図を実現することが可能となる.

クラスタ図をいつでも取り出せるようにしておくと便利だが,それにはメニューにコマンドを追加したりなど,いろいろと下準備が必要だ.いまのところは必要に応じてプログラムの中から直接実行するだけとしておこう.クラスタ図はZTで処理されるCSVファイルなので,多重枝の存在は邪魔になる.これを取り除いたデータを出力するようにしたい.

ゼルコバの木モジュール構成図 TEST2.ZELの標準家系図 基準ノード=#61 NLISTを軸線図法で開いて,TOPOLOGY::CallBuildShaftLineで停止した.軸線取得に失敗している.「ループフリーグラフで自己ループ枝の登録は不可」というエラーが出ているので,これが関係しているのではないか?FindJikusenAncestorでは,「基準ノードの自己ループ枝を登録する」としている.ただし,奇妙なことにこの枝は直ちに削除されている.もし,これが基準ノードをグラフの節点として登録することが目的の操作であるのなら,明示的に節点を追加した方がよい.

修正を入れてみたがエラーは解消しない.軸線グラフに自己ループを許す属性を与えてオリジナルのコードに戻しても同じ.出力を見ても確かに軸線は崩れている.

image

軸線グラフの節点はカード,枝は親子関係で,基準ノードの先祖ノードと末裔ノードを結ぶ最長経路を探索するために生成される.この検定は2つのパートに分解できる.①先祖ノード→基準ノードの最長経路を見つける,②基準ノード→末裔ノードの最長経路を見つける.おそらく先祖ノードは検定に入る前に決まっていると思われるが,どのカードが末裔ノードになるのかはこの時点では不明だ.

ここで使われている軸線グラフは比較的単純な非循環的有向グラフになると考えられるので,クラスタ検定でやったのと同じ手法でZTのCSVファイルとしてエクスポートできると思う.各段階ごとにそれをやれば,どこでしくじっているのか一目瞭然に分かるのではないだろうか?それほど難しくないのでやってみることにしよう.⇒できた!こんな単純なグラフで失敗するなど考えられないくらいシンプルなグラフだ.

image

基準ノードはcouplingで,先祖ノードもcoupling,末裔ノード候補は2つあるが,長幼の順に従えば,toplistだろう.ファイルをインポートするときエラーが3件発生している.いずれも「兄弟順位重複」でこれは「続柄」をすべて(1)としているのだから,当然だ.「名前を付けてZELファイルとして保存した後は正常に使用できます」ということでよい.FindJikusenAncestorの出口でグラフの状態を見てみよう.

image

余分なカードが2枚残っているが,これは余分な枝だけを削除してカードは放置されているためだろう.刈込めば余分なノードも一掃されると思われるが,とりあえず,これでも問題はないはずだ.それではどこで失敗しているのだろう.上掲した最終出力を見ると,軸線は4段目のNLIST→baselistで折れ曲がり,その上のtribelistから先祖ノードのcouplingまでの間には軸線ノード(イエロー)が存在しない.FindJikusenAncestorですでに末裔ノードから先祖ノードまでの直線経路が発見されているのに,なぜこんなことになってしまうのだろう?

一旦軸線上のノード(軸線ノード)が決定されれば,軸線を描画するのは難しくない.これらのすべてのノードが親子関係で繋がっているということは,先祖ノードを除けばそれらのノードを格納する結婚枠が存在するということであり,それらの結婚枠のgoodsonに軸線ノードを指定するだけで完全にストレートな軸線を描画することができる.goodsonというのは,結婚枠の吊り位置に当たるノードで,親の結婚点の直下に配置される.軸線枠はFindJikusenAncestorで決定されているのに,軸線ノードの決定を保留しているという理由が分からない.

FindJikusenAncestorは系統並び替えのステージ9の本検定(系列分解)でTribeDecompositionの中から実行されている.この段階ではノードの有効/無効はすでに決定しているから,FindJikusenAncestorでの決定は最終決定になり得ると思われるのだが… この処理をTribeDecompositionの冒頭で実行しているのは,系列分解に先立って軸線先祖を決定する必要があるためと考えられる.もし,留意点があるとすれば,軸線枠となるべき結婚枠は必ず軸線ノードの下で展開されなくてはならないということくらいだ.

この後,TribeRelocationに入り,BUILDCENTERLINEフェーズでCallBuildShaftLineが呼び出され,その中でBuildShaftLineが実行される.軸線ノードはここで最終決定される.BuildShaftLineでは軸線グラフを参照してはいるが,むしろ現物の系図木上の接続関係をトレースしている.BuildShaftLineの出口で軸線グラフがどうなっているのかを見ておこう.意図的にそうしているのかどうか?わからないが,壊滅状態だ.つまり,枝を完全に失っている.BuildShaftLineのステージ【2】の出口ではすでにtribelistから後ろの枝を失っている.ステージ【1】ではグラフを操作していないので,まだすべての枝が残っている.

image

なるほど,なんでこんなことをやっているのかという理由は分かる.つまり,軸線グラフのノードはCARDLINKであり,NAMEBOXではないからだ.CARDLINKは論理的な人名オブジェクト,NAMEBOXは画面上に描画される描画オブジェクトだ.CARDLINKとNAMEBOXは1対多の関係にあるから,具体的に展開された描画リスト上でどのNAMEBOXが軸線上に来るかは改めて決定される必要がある.

しかし,上述したように,①軸線ノードはFindJikusenAncestorの段階で決定できる,②軸線枠の軸線ノードの下で展開されなくてはならない,という2項が満たされれば,自動的に軸線が描画リスト上に構成されると考えて間違いないと思う.それを強制するのは難しくないはずだ.この2つが実現されれば,BuildShaftLineという処理はまるごと不要になると考えられる.⇒いや,そもそもCARDLINKはjikusenというプロパティを持っていない.eldestsonというフラグは持っている.「軸線図で長子相続者であるか否かを判定するためのフラグ」という説明が付いているのだが,使われているのだろうか?

長子相続図と軸線図は両立するだろうか?並立しないような気がするのだが,間違いだろうか?基準ノード→先祖ノードと基準ノード→末裔ノードというパスは親子関係で接続されているのだから,「長子」が入ってくる余地はないように思われる.「長子相続の場合はeldestsonを立てる→廃止 @20180818」というコメントが付いている箇所もあるが,実際には18箇所くらいで参照されている.多分このフラグは廃止できると思われるが,暫時放置して,別のフラグを立ててみることにしよう.

一応要件の①と②は実装したが,状況はまったく変化しない.おそらく,BuildShaftLineがすべてをぶち壊しているのではないかと思われるが,どうしたらいいだろう?現行のBuildShaftLineの論理をすべて捨てて,上記のセオリー通り「先祖ノードを除けばそれらのノードを格納する結婚枠が存在するということであり,それらの結婚枠のgoodsonに軸線ノードを指定するだけで完全にストレートな軸線を描画することができる」という指針に従って,軸線ノードを軸線枠のGoodSonに設定してみたが,事態は却って悪化してしまった.

これは思ったより手強いかもしれない.⇒ミスっていた.動きそうだ.要件がもう一つある.軸線枠はTYW婚にならないという点だ.抽出枠にするというのはどうか?それも問題だと思う.⇒対処した.

image

ここまで来ればもう一息だ.

多重カードゼロの超ユニバーサル系図ソフト

▲Z木家系図 基準ノード=#104 藤壷の宮を開いて,系統並び替えの出口検査で垂直スプリットが検出された 

この問題は2021/01/23 InvestigateVSplits 垂直スプリット発生 で既出している.このときは,重婚クラスタ循環が存在する場合には,ShiftDirectAbsoluteを実行しないということで切り抜けたが,多重カードが存在する場合にもShiftDirectAbsoluteを実行するというのが現在の方針なので,別途対策を考えなくてはならない.垂直セグメント検定が廃止されたのは2018/02/26でコードは残ってはいるが,もう一度復活させるというのは上策とは言えない.

セーフティネットとして整備しておくという考え方もあるとは思われるが,TestVerticalSegmentを実行するためにはかなりの下部機構を再整備しなくてはならないので,コストパフォーマンス的に引き合わないように思われる.すでにクラスタ循環が存在する場合でもクラスタを世代分割して対処する方策が編み出されているのでその方向で収拾を図るべきだろう.問題を解析してみよう.

4段目(物理世代3)にスプリットの入った図面を与えられた絶対世代番号に従って描き直してみると下図のようになる.光源氏(1)は絶対世代番号3を持っているのでちょうどスプリットで空いた部分に配置される.

image

上図のような結線が許されるのなら,ShiftDirectAbsoluteを使って絶対世代番号に準拠した配置を実現できるが,果たして可能だろうか?という前にこのような図式は妥当と言えるだろうか?異なる世代にある藤壷の宮→光源氏の結婚連結線をどう引くかというのが問題だ.図を観察してみよう.結婚点◆が藤壷の下にあり,◇が光の下にあって,この2つのマークを線分が連結するというのが上図の構図だ.

もし,これが許されるのなら,これまで問題にしてきた,「婚姻当事者は同世代に配置されなくてはならない」という原則を大幅に逸脱することになる.つまり,「婚姻当事者の世代は問わない」ということになり,それを聞いて大喜びする人もいるかもしれない.もともと,重婚クラスタに循環が生じない限り異世代婚姻というのは許容されてきた.しかし,これが上図のようなところまで緩和されると,もはや「多重カードの発生要因は存在しない」という世界が実現することになる.

これでよいのだろうか?ゼルコバの木はこれまでも Politically/Socially Neutral という立場を保ってきたが,「一般に世代差のある婚姻にはつねに多少の問題がある」というのが開発者自身の個人的見解である.

しかし,逆にまた,一介の数理エンジニアとしての立場からすると,「多重カードゼロのユニバーサルな系図作成ソフト」を実現するというのはチャレンジングな課題であり,大いに興味をそそられるところがある.上の図面を見た限りでは,テクニカルには実現困難であるようには見えない.世界中のありとあらゆる系図図面が多重カードなしで描画できるとしたら,確かにそれは画期的なことであるような気がする.少なくともわたし自身はそんなものできる訳がないと考えてきた.

図形言語として考えた場合には,世代差のある結婚においても連結線はつねにある結婚点◆と他の結婚点◇を結ぶものであり,(結婚点を一回だけ通る)親子連結線と読み間違えられる可能性はないという点において文法的には正当と言える.実現可能性を考えるために,具体的に構想設計してみよう.これは両手に花技法の一種の拡張である.

BTW(Between Two Women)は「両手に花」と呼ばれているが,2つの結婚を扱うものではなく,対象となる結婚枠はつねに一つだ.つまり,カードR,カードLという本人ポジションのカードが2つあり,両者の間に結婚M=L+Rが存在し,MがRの下で展開されているとき,配偶者M.spouse=(L)を消去してLとRをBTW連結線で結ぶというのがBTWの作法だ.このような結婚枠をBTW右手結婚枠,Lを左手本人,Rを右手本人,消去されるM.spouseを右手婚配偶者と呼んでいる.

ここで右手婚配偶者を消去しなかったらどうなるか?右手配偶者を消去しないというのはその下に垂線を配置するための空間をリザーブするためだ.この可視の右手婚配偶者を抽出枠でラップして垂直にシフトし,左手本人の世代まで下げることまではできそうだ.そこまでできれば,あとは抽出枠チェーンを通る垂線を描画し,左手本人の結婚点と連結する水平線を描画すればよい.拡張BTWでは右手本人/左手本人の代わりに上手本人/下手本人と呼ぶことにしよう.拡張BTW(XBTW)構成のおおまかな手順は下記のようなものになる.

  1. 世代差のある結婚Mが存在する場合は,Mのownerを上手本人,spouseを下手本人に切り替え,Mを上手本人の拡張枠に移動してXBTW上手結婚枠とする
  2. 上手婚配偶者だけを含む抽出枠を生成し,下手本人世代までシフトとしてその間を長い尻尾で接続する
  3. 描線は,①上手本人結婚点→抽出枠中心線までの水平線,②下手本人結婚点の高さまでの長い尻尾垂線,③下手本人結婚点→抽出枠中心線までの水平線の3本
  4. 2つの結婚点のどちらが◆となり,どちらが◇になるか,つまり結婚枠のholderがどちらになるか,また,水平連結線を通すチャンネルの確保は系統並び替え出口のCheckAtypicalMarriageで実施する

方式的にはこれしかないような気がするのだが,その場合厳密には上掲したような図にはならないと考えられる.これは配偶者は一般に本人の左側に配置するというのがZTでの決まりになっているからだ.描き直してみると下図のようになる.

image

図式的にはこれで通るとは思われるが,水平線の途中から唐突に分岐するというのも意味論的に判り難いところがあるので,おそらく下図のように描画することになるのではないだろうか?

image

XBTWというのは相当程度特殊なものであり,出現する可能性もまれであると思われるので,BTWとは別に完全に独立のコードとしてもよいのではないか?最後のCheckAtypicalMarriageだけは共通化するしかないが,この方が扱い易いのではないかと思う.問題はこれだけの仕掛けですべての場合をカバーできるかどうか?という点だ.実際,世代差のある結婚がかなり離れて存在する場合があり,その中で上手本人と下手本人を結ぶ垂線がつねに描画できるかどうかという点も微妙だが,その辺りは,まぁ,やってみなければ分からないとしておこう.

ZTが「多重カードゼロの超ユニバーサル系図ソフト」に生まれ変わるかどうかという瀬戸際だが,先に進む前に一つ片付けておきたいことがある.重婚グラフと呼んでいるものは重婚クラスタを節点とし,親子関係を枝とする(非循環的)有向グラフだが,その具体的な形姿をZTを使って画面上で見てみたい.方法としては,系図情報をCSV形式で出力し,別アプリで開くということが考えられる.DLLからVBに一覧表データを送るときの形式はすでにタブ区切りデータになっているので,それをファイルに直接出力するだけでよいのではないかと思う.

一行分のレコードの内容には,番号,氏名,性別,よみ,所属,肩書き,郵便番号,電話番号,現住所,生年月日,没年月日,本籍地,婚歴,生没,血液型,干支,星座,出生地,携帯番号,E-mail,父母数,父番号,母番号,続き柄,配偶者が含まれるが,重婚クラスタの親子関係は単身婚なので,必要な情報は

番号,氏名,父母数,父番号

だけと考えられる.従って,重婚クラスタグラフを出力するのはそれほど難しくないはずだ.重婚クラスタの中身は人名カードのリストなのでその情報もほしいところだが,代表ノードの名前を氏名欄に出力しておけば,多少の手がかりにはなるだろう.

重婚クラスタグラフ出力用の専用関数としてTOPOLOGY:SaveClusterGraphを作ってみたが,まったく動作しない.一つには一覧表データとして送っているデータの列の並びとVBでエクスポートしているファイルの列の並びが一致していないという問題があったが,それだけでなく,そもそも現行バージョンでエクスポートしたファイルをインポートすることができない.インポート・エクスポートに関わるロジックは一度作ってからはまったく触っていないはずなのだが…

薫の傍系血族図で水平スプリット

源氏物語全系譜6.1.ZELの傍系血族図を基準ノード=#74 薫でソートして,系統並び替えの出口で水平スプリットが検出されている.

image

確かに始系列の摂政太政大臣系列で女一宮 (冷泉院の)と若君 (紅梅の)の間が開いている.しかし,このギャップは結婚枠間のギャップとほとんど同じ程度に見える.現行では検査に用いている枠幅は結婚枠間ギャップ+1となっている.これでは厳し過ぎる.検査枠幅を結婚枠間ギャップ+許容最大誤差とすればパスできる.このサンプルでは結婚枠間ギャップ13,許容最大誤差3となっているので問題ないのではないだろうか?その代わり,検査単位を結婚枠間ギャップとした.

これでこの問題は解決したはずだが,今度は光源氏の傍系血族図で同じエラーが出るようになった.

image

結婚枠間ギャップは結婚枠の左側に入れているので,それと系図枠外の余白を含めてスプリットとみなしているのだろう.検査単位を元のように結婚枠間ギャップ+1とし,検査枠幅=検査単位+許容計算誤差-1とすることにしてみよう.⇒これで収まったようだ.

同上サンプル傍系親族図を#102 花散里でソートして,NAMEBOX:CheckDummyBoxSerialで停止した.(elder != TYW->GetUpperNode())というエラーが起きている.⇒この検査コードは2021/01/23 ダミー枠チェーン上の結婚枠直列検査 で修正したときに追加されたものだが,間違っている.TYWの上位ノードはTYW枠が接続しているダミーノードであり,必ずしもLDRであるとは限らない.これは「TYW婚との擬似ダミー枠直列」と呼ばれるものでノーマルな状態と考えられる.

image

このサンプルでは多重が11件発生している.削除された循環枝数は2,婚姻グラフから削除された枝も2しかないのに,多重が11もあるというのはかなり問題だ.削除されているのは女三宮+柏木と紫の上+光源氏の2つだけだ.重婚クラスタ図が表示できれば,なぜこういうことになってしまうのか?もう少し突っ込んだ解析ができるのだが…

傍系親族図 基準ノード=#126 源氏宮を開いて,Bobject:getCommonPairTypeで停止した.PAIRLIST::searchCommonPairでPAIRBOX #2614:#2611 冷泉院(3)→#1597(1)の端点共有先を探しているところだ.ノード対区間がゼロに近いため,getCommonPairがCOMMON_NOCOMMONを返している.⇒getCommonPairType,およびsearchCommonPairでゼロ復帰するようにした.

このサンプルは多重が16も出ている.除去された循環枝は2,婚姻枝を4除去するだけで循環は止まっているのだが…

image

法定親族図 基準ノード=#94 明石の上を開いて,NAMEBOX:getPairlistで停止した.障害が起きているのはNAMEBOX #1297 八宮(0).物理世代番号は9でGENELIST::GetGeneBoxが空を返している.⇒オプションで停止していた「adjustGenerationRangeを停止する@20210117」を復活させて問題なく動作するようになった.

image

傍系親族図 基準ノード=#98 夕顔を開いてMARGBOX:checkBrotherOrderで停止した.関数内で無限ループしている.障害ノードはNAMEBOX #2559 夕霧(1)で結婚枠の先頭ノード.GetContactDistanceで移動量を計算し,CheckBrotherCrushで調整しているが,状変なしでループしている.⇒MoveBundledLDRでLDR束の隣接ノードまで左移動を実行しているが,移動のターゲットノードが移動対象ノードを親参照しているため距離が変化しない.⇒対処した.

法定親族図 #167 藤中納言(竹河)を開いて,NAMEBOX:previousNameBoxで停止した.(PHASE > CLEARTABLE && !showUnderWear && !OnRetrieveGhost && familytree && !OnSetPartialList && getvisible() && nbox->nextNameBox() != this && !disposing),つまり,nbox->nextNameBox() != this という理由だ.言い換えるとpreviousNameBoxがnextNameBoxの逆関数になっていない.障害が起きているのはNAMEBOX #2101 明石中宮(3)で,previousNameBoxでは明石中宮(3)→紫の上(若紫)(1)であるのに対し,nextNameBoxでは玉鬘(1)→紫の上(若紫)(1)が返ってくる.

MARGBOX #1101:#1249 光源氏(0)+→#1610 紫の上(若紫)(1)
R :#1610 紫の上(若紫)(1)
L :#1216 [結婚枠]:[3]
R | :#1611 玉鬘(1)
L | :#1103 [結婚枠]:[3]
R | | :#2101 明石中宮(3)
R | | | :#2144 [結婚枠]:[4]
R | | | | :#2143 明石中宮(4)
R | | | | | :#2102 [結婚枠]:[5]
R | | | | | | :#1612 明石中宮(2)

#1611 玉鬘(1)の入っている結婚枠は不可視で幅ゼロの空枠になっているため,previousNameBoxではValidMargBoxではないと認めてパスしているためだ.nextNameBoxでは,「不可視結婚枠の中にもLDRノードが存在する@20171011」として特に制限を設けていない.⇒previousNameBoxのValidMargBoxという制限を外してエラーは解消した.これでよいと思う.

image

この図面では多重は1件だけだが,光源氏のカードが3枚も出ている.上段の先祖ポジションにいる光は一段下げることができるはずだ.そうすれば藤壷の宮のボックスに入っているカードは不要になるはずで,紫の上の隣のポジションは外すことはできないが,多重カードを2枚までは削減できるはずなのだが…

▲Z木家系図 基準ノード=#104 藤壷の宮を開いて,系統並び替えの出口検査で垂直スプリットが検出された.

image

重婚クラスタ循環の世代分割問題をクリア

重婚クラスタ循環を世代分割するという課題を原理的にクリアして,そのブラッシュアップを進めているが,BuildSameGeneMarriageGraphで無限ループするようになってしまった.どこかでミスっているのだが,どこで失敗しているのか特定できない.やむを得ず昨日の修正を一旦破棄して,2021-01-28の始業時バックアップに戻ることにする.昨日はまったくログを取っていないので,修正手順を再現しながらステップを追って点検してみよう.⇒バックアップはとりあえず動いている.ここからもう一度出直すことにしよう.先に進む前にまず,足元を固めるために,仕掛りになっている部分をフィックスしてしまった方がよいと思う.以下のような懸案事項がある.

  1. SIMPLEGRAPHのattributeを廃止@20201105 抽象グラフ検証系でオブジェクトではない一般データを扱えるようにするための修正だが,方針を変えて,attributeとncidを併用する方式に切り替える必要がある
  2. グラフのattributeはこれまで有意に使用されていなかったが,「多重枝」を扱う必要があり,有向グラフと無向グラフを峻別する必要も出てきたのでより重要性が増している 自己ループという属性もある
  3. SIMPLEEDGEの持っている(一部)属性をアプリに開放し,アプリが自由に使える領域に指定する 逆にグラフ検証系ではこの「ユーザパラメータ」は操作しない
  4. SIMPLEGRAPHクラスを基本クラスと応用クラスに再編する 前にUNDOシステムで行った改編と同様趣旨の修正を行い,SIMPLEGRAPHの抽象度を高める
  5. adjustGenerationRangeを停止する@20210117 動作的にはこの関数をNOPにしても問題は生じていない これが正しいとすれば,系列枠クラスのいくつかのパラメータを廃止してadjustGenerationRangeを完全に破棄すべきだろう
  6. 不可避の多重カード数と重婚クラスタ循環を分解するために除去される最小枝数が完全に一致するという予測が出てきた
  7. 重婚クラスタ循環が存在する場合と存在しない場合の論理を統合する
  8. 右手配偶者が左手本人で停止しない@20210108

結構クリティカルな修正だが,まずこれらを片付けてから前に進むのがよい.その前に上記を除く仕掛りを現状でフィックスしておこう.

  1. 重婚クラスタ循環を解決する@20210127 1箇所
  2. sameGeneCyclesを無視する@20210127 3箇所
  3. 仮修正 4箇所,暫定

まず項目1から始めよう.この修正は簡単なので,オプションを廃止し,コードを直接修正することにする.⇒しかし,これには昨日の「initializeではSIMPLEGRAPH:attributeを更新しない@20210128」が関係するので,まず,この修正を入れておいた方が安全だ.attributeはオブジェクト生成時に指定するか,ないしsetAttributeで明示的に設定するかのいずれかに限定するというのがこの修正だ.この修正は8箇所ある.⇒修正した.この修正はこのままフィックスする.⇒これでグラフ検証系で非モノデータを扱える準備ができた.

項目2 ⇒これは昨日の修正「無向グラフでは並行枝を登録しない@20210128」に関わりがあるので,後回し.項目3,ユーザに解放する領域(ユーザ変数)とは以下を言う.

  1. nodule *reffer;    // 参照ノード1
  2. nodule *reff2;    // 参照ノード2
  3. int usable;        // 有効な枝
  4. int nouse;            // 切断された枝
  5. int circular;        // 循環する枝(閉路上の枝)
  6. int selfloop;        // 自己ループ枝

これらはユーザが独自に定義して自由使用できる変数とし,アクセス関数は設けない.ただし,selfloopはグラフ処理系でも判断できるので,グラフ関数で設定した方がよいかもしれない.SELFLOOPGRAPHというグラフ属性を追加したので,selfloopはSELFLOOPGRAPH属性を持つグラフに対してのみ許すとした方がよいと思う.circularを閉路上の枝と定義すれば,グラフ処理系で操作する対象となるが,この変数は必ずしも閉路上の枝にかならず設定されるというものではないので,やはりアプリ側で管理するとした方がよい.現行ではおおよそそのような棲み分けになっていると思われるが,項目4を実施すればはっきりするだろう.

その前に昨日の修正「無向グラフでは並行枝を登録しない@20210128」を片付けておこう.⇒修正はSIMPLEGRAPH::addとfindedgeの2箇所だけだ.⇒問題なく動作している.現行では有向グラフを使っているのはTribeTreeGraph(系列木枝グラフ)だけのようだ.しかし,多重グラフ2も有向グラフのはずなので,設定してみよう.まずい.どこかで問題のある修正を入れてしまっているようだ.重婚同類=0 多重=10になってしまっている.上記の「無向グラフでは並行枝を登録しない@20210128」という修正だ.⇒初歩的な論理ミスがあった.

多重グラフ1(婚姻関係グラフ)で「ループフリーグラフで自己ループ枝の登録は不可」というエラーが出ている.婚姻グラフでは単身婚を自己ループとして登録している.この枝を弾いても動作にはほとんど影響は出ていないものと見られるが,単身婚以外の結婚を持たないノードの子ども世代とこのノードの親ノードの世代で重婚クラスタ循環が起きる可能性はゼロとは言えないので,このグラフが自己ループを持つのを認めることにしておこう.

系列木グラフでも同様のエラーが出ている.系列木グラフでは始系列を自己ループで登録している.これはこれでよいのではないだろうか?系列木グラフにもSELFLOOPGRAPH属性を与えておくことにしよう.グラフが大分精密に操作できるようになってきた.項目6の「不可避の多重カード数と重婚クラスタ循環を分解するために除去される最小枝数の一致」という予測を確認するためには昨日の修正を入れてみる必要がある.「多重婚配偶者は切り離す@20210128」という修正だ.⇒どうもこの修正はまだ収束していないようだ.

TestInevitableMultiZeroに入って,TestStronglyConnectedで停止してしまう.BuildSameGeneMarriageGraphのループでTestStronglyConnectedでなくなるまでループしても同じ結果になる.これは,BuildSameGeneMarriageGraphの出口で循環を停止するために除去した枝を戻しているために起きているが,除去される枝はすべて循環の原因となる親子枝であり,この循環は婚姻関係グラフで障害ノードを切り離すカットセット枝をすべて除去しているのだから,循環枝を戻しても再び循環が起きることはないはずなのだが…

コード的には「多重婚配偶者は切り離す」という論理を追加しない方がシンプルであり,ある意味でこれで十分だとは思われるのだが,なぜエラーが発生するのかその理由だけは調べる必要がある.BuildSameGeneMarriageGraphのループのボトムでRetrieveRemovedEdgeを実行すると,今度はループが停止しなくなる.循環枝は9本で,多重配偶者を3件処理している.

  1. card=CARDLINK:#410 @3桐壷の更衣[0] wife=#408 桐壷院 @2
  2. card=CARDLINK:#554 @75葵の上[0] wife=#406 光源氏 @1
  3. card=CARDLINK:#656 @126源氏宮[0] wife=#488 朱雀院 @42

この後,【CARDLINK:#406 @1光源氏[0]】⇒【CARDLINK:#414 @5紫の上(若紫)[0]】の循環枝の処理に失敗してループしている.この親子枝はすでに一度処理されているものなのだが,なぜかぶり返している.光源氏は循環枝の親としてすでに2回登場しているので,光源氏の配偶者はすべて光源氏のブロックに移動しているはずなのだが,紫の上は光源氏の配偶者であると同時に養女でもあるので,配偶者として移動してもそのブロック内で自己ループになるということは考えられる.しかし,だとすれば,この手法を使わない場合でも同様のことが起きなくてはならないのだが,そうならないのはなぜか?

いや,逆に言うと,既定の方式でなぜ問題が解けているのか?という方が疑問だ.既定の方式では光源氏と紫の上は同じクラスタに残るはずであり,そのクラスタでもう一度自己ループが発生してもおかしくないのだが… できるものなら,この重婚クラスタ図をZTを使って表示してみたいのだが… 重婚クラスタ図は原理的に系統図とまったく同じなので,接続関係をZTに渡すことができれば描画は容易にできるはずだ.一つのアプリで2つの系図を同時に開くことができればそれも可能だが,現状では直ちには応じることはできない.

一覧表形式にして出力することは不可能だろうか?かなり面倒くさそうだが,不可能ではないと思う.CSVで出力できれば別アプリで開くこともできる.やってみる価値はあるとは思われるが,そのためにはかなりの準備が必要だ.いや,思ったより簡単なのではないか?重婚クラスタ図には親子関係しかないので,系図データとしてはかなり単純なものになるはずだ.この図面のノードはクラスタでそれに親子関係を与えたものがクラスタ図になる.ただし,この図面ではそのクラスタに含まれる結婚までは分からない.それをチェックするだけなら,むしろ既存のダンプルーチンで十分だ.

とりあえず,重婚クラスタグラフの連結成分リストをダンプすればよいのではないか?ループ出口では重婚クラスタグラフの連結成分リストは空になっている.むしろ,婚姻関係グラフの連結成分リストを見た方が早い.⇒光源氏と葵の上は同じクラスタ,紫の上,藤壷の宮などは孤立ノードになっている.以下のようなクラスタが存在する.

  1. 桐壷院,桐壷の更衣
  2. 朱雀院,源氏宮
  3. 式部卿宮,紫の上の母
  4. 明石入道,明石の尼君
  5. 紅梅,真木柱,蛍兵部卿宮
  6. 光源氏,葵の上
  7. 摂政太政大臣,大宮
  8. 柏木,落葉の宮,夕霧,雲井の雁
  9. 按察大納言(桐壷),桐壷の更衣の母
  10. 今上,明石中宮
  11. 匂宮,中姫君,匂宮の北の方,浮舟,薫,女二宮
  12. 冷泉院,秋好中宮,王女御,弘徽殿女御,冷泉院の御息所
  13. 按察の大納言(若紫),北山の尼君
  14. 先帝,先帝の后宮[0]
  15. 春宮,春宮の女御,麗景殿女御
  16. 二宮,二宮の北の方

意外に細分割されている.光源氏+紫の上の関係が切断されるのは,葵の上→夕霧という親子関係から,葵の上の配偶者光源氏の配偶関係が切断されることによって初めて発生する.従って,葵の上→夕霧という関係が存在していなければ同じ障害が発生する可能性はあるように思われる.従って,選別的に切断するのであれば,「多重婚配偶者」ではなく,むしろ「親子関係にある配偶者」ないし,「直系血族関係にある配偶者」を切断しなくてはならないということになるはずだ.

直系血族関係をチェックする既存関数としてIsAncestorOf,IsMyParentがある.前者は主語の下流系を探索し,後者は上流系を探索するもので,組み合わせればよさそうだが,前者はすべての人名リンクを探索しているので今の場合には適切ではない.後者は無効カードを除外しているが,特殊な場面で使うことを予定しているため応用できない.新たに一つ作ってみよう.⇒CARDLINK::IsMyAncestorという関数を新設した.IsAncestorOfとは逆に上流方向を探索するものになったが,紫の上を「直系血族配偶者を発見」として検出できている.

IsAncestorOfは現在使われていない関数だが,TestInevitableMultiZeroのすぐ下にあったところから推測して,重婚クラスタ検定の中で使う意図で開発されたものと思われる.クラスタの親子関係接続による循環を検査すればこと足りるということになって放置されていたのだろう.一応これで解決を得たが,このアルゴリズムで完全と言えるかどうか?については,確信が持てない.一番確実なのは,配偶者→配偶者の配偶者の枝をカットするのではなく,本人周辺の枝をカットして孤立ノードにしてしまうというのが一番確実なのかもしれないが…

祖父が孫と関係するなどのこともあり得るので,親子関係だけでは十分ではないということだけは確実だ.現行論理でも直系血族間で婚姻関係があれば,必ずクラスタ循環が発生するから検出は可能だが,現行のやり方では解けない場合が出てくる可能性がある.循環は垂直性の循環なのでそのどこを切っても循環は一応止まるが,解決にはなっていない.そのようなことが起こり得るということは上の例からも明らかだ.

現行では親ノードの配偶者をカットしているが,むしろ子どもの配偶者をチェックすべきではないだろうか?この方が確実にカットできるような気がする.親ノードの配偶者を周辺で切り取り,子ノードの直系血族配偶者をカットというのがわかり易いような気がする.⇒実装した.多分これが正解だと思う.循環枝を1本処理した段階で一度ループから離脱して検定を再実行するようにして,処理された循環枝3,削除された枝6に変化した.出力にも少し変化が現れた.

image

多重になっているのは,光源氏,藤壷の宮,柏木の3件だ.これらのカードが多重になっている原因は,光源氏の場合,紫の上が養女であるという直系血族との婚姻,藤壷の宮の場合は桐壷院・光源氏という親子との三角関係(俗に言う親子丼),柏木のケースは配偶者の落葉の宮と女三宮がいずれも重婚者,つまりダブル三角関係という同情の余地もない情況だ.図面も以前出ていたものとは大分トポロジーが変わっているが,今回の方がよりわかり易くなっているのではないかと思う.多分これが重婚クラスタ循環問題についての最終解となるだろう.