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

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

重婚クラスタ循環の多重を最小化する世代分割は可能か?

系図作図上の要点は多重カードの出現を極小化することにある.カード多重の原因が重婚クラスタ循環の存在にあることは2017年初頭にはすでに突き止められていたが,それから四年経過した今もなおパーフェクトな解法は得られていない.つまり,重婚クラスタ循環が存在しないときに多重カードゼロの図面を描画することはできているが,クラスタ循環が存在するときの多重カードの最小化には成功していない.と言うか,この部分は手つかずで放置されていた.今回はこの「系図作成上の最大の課題」に最終的な決着を付けたいと思う.

配偶関係(婚姻関係)によって相互に連結したカードの集合を重婚クラスタと呼ぶ.配偶関係は排他的な同値類を構成するので,ある重婚クラスタに属するカードが他のクラスタに属するということはあり得ない.サイズが2より大きいクラスタがあるとすれば,そこには必ず一人以上の重婚者がいると考えられる.重婚クラスタを節点とし,親子関係を枝とするグラフを重婚グラフBとする(クラスタCに属するカードPの子どもQがクラスタC’に入っている場合,C→C’の枝が生成される).親子関係には方向性があるので,グラフBは有向グラフであるとする.

グラフBが循環(閉路)を含んでいないときには,多重カードの存在しない図面が描画できることは明らかである.グラフBの連結成分を重婚クラスタ属とする.重婚クラスタ属とは親子関係によって垂直に連結された一群の重婚クラスタの集合である.有向グラフは任意の2節点間に有向経路が存在するとき,強連結であると言う.強連結グラフ上の任意の有向枝は必ずいずれかの有向閉路上にある.

※重婚クラスタ属とはZTシステムの用語で言えば「系統」に相当する.つまり,始系列と直接/間接に接合した複数の系列の集合と等価である.

有向グラフBの重婚クラスタ属Gが強連結であるか,強連結成分を含んでいる場合,つまり,クラスタ属Gが循環(閉路)を含んでいる場合には系図図面上には多重カードが発生することは避けられない.これを不可避の多重カードと言い,多重カード発生の要因となるようなクラスタ属に含まれる強連結成分(孤立ノードを除く,ただし自己ループを持つノードを含む)を重婚クラスタ循環と呼ぶ.

重婚クラスタ循環(循環を持つ重婚クラスタ属に含まれる強連結成分)から循環の要因となる親子枝を除去することにより重婚クラスタ属が循環しないようにすることは可能だが,一般に,このような枝(循環枝と称する)を除去しても事態は何ら好転しない.というのは,重婚クラスタ循環の要因となる「循環枝」はほとんどの場合,「自己ループ」であるからだ.と言うか,手持ちサンプルでは例外なく自己ループであるとしてもよいのではないかと思われる.

親子枝が自己ループになるというのは,ある重婚クラスタの中に親とその子どものカードが同時に含まれている場合に発生する.婚姻関係ないし配偶関係にある2つのカードがつねに同世代に配置されなくてはならないことと同様に,親子関係にある2つのカードでは少なくとも親カードは子どもカードよりも上の世代に配置されなくてはならないことは明らかだ.従って,親子枝が自己ループになるとすれば,この婚姻の水平的関係と親子の垂直的関係が直交する(両立しない)矛盾と言える.

重婚クラスタ内のノードは同一世代に配置することが求められているが,それが不可能であるとすれば,クラスタを一度世代別クラスタに分割する以外の方法はない.その方法を考えてみる.

  1. 重婚クラスタ循環から循環がなくなるまで親子枝(P, Q)を除去する
  2. 重婚クラスタ循環から除去された枝の集合を循環枝集合Sとする
  3. 重婚クラスタ循環に含まれるカードを節点とし,配偶関係を枝とする無向グラフを循環グラフJとする 仮定により,グラフJは連結であるが,内部に閉路を持っている場合もあり,持たない場合もある
  4. 枝集合Sから循環枝を1個取り出し,親子枝(P, Q)とする.
  5. ノードPないしQのいずれかがグラフJに含まれない場合はステップ8へ
  6. 枝(P, Q)の親ノードPと子ノードQを分離するようなグラフJのカットセット(切断枝集合)を求めて,グラフJから除去する
  7. 親ノードPを含むパートに含まれるノードをグラフJから除去する
  8. ステップ4に戻って枝集合Sが尽きるまで繰り返す
  9. ステップ5のカットセットに含まれるすべての枝を元の検定グラフ1から除去して,重婚クラスタ検定を再実行する

この解析の目標はステップ6のカットセットを最小化することだが,おそらく,除去されたカットセットの枝数と表示される多重カード数が(ほぼ)対応するのではないかと推定される.カットセットが最小であることを求めないのであれば,カットセットの枝集合を決めるのは簡単だ.親ノードPの周辺の枝をすべてカットすれば,Pは孤立ノードとなり,Qはそれ以外のすべてのノードと連結したままになる.その逆に,最小カットセットを求める問題はNP完全ではないかと思われる.たとえば,2つのパートの節点数が等しくなるようなk-カットセットが存在するか否かを問う問題はNP完全であることが知られている.※

※グラフの節点を2つのパートに任意に分割したときのカットセットを求めるのは難しくない.かき混ぜた納豆をスプーンにすくって取り分けるときに長く伸びた糸をくるくるっと回して切れば,それが「カットセット」であり,どのような分割でもカットセットは一意に決定できる.

次善の策として,ノードPから距離1だけ離れた枝を削除するということが考えられる.ノードPに隣接する枝はすべてPの配偶者だが,その次の枝はPの配偶者の配偶者への枝になる.事例から見ると重婚クラスタ循環の親子枝の親となるようなノードは通常重婚者であり,それも多重婚者である場合が多い.たとえば,桐壷院は配偶者を6持っているし,光源氏は13も持っている.従って,Pの隣接枝をカットするやり方では最小どころか,最大カットセットになりかねない.Pの配偶者が重婚者である可能性はゼロではないが,確率的にはそれほど高くない.

もし,この方式でよいとすれば,グラフJを新たに起こさなくても,元の検定グラフ1上で操作できるかもしれない.実装はそれほど難しくないので,試してみることにしよう.⇒うまくいったようだ.多重は3件まで減少した.まだいくつかエラーが出ているが,描画はほぼ完璧だ.

image

画面の下の方にあった多重は完全に解消した.下図は修正前のバージョンで描画したものだが,これまでは本来クラスタ循環と関わりのない部分で多量の多重が発生していた.これは重婚クラスタ循環で生じる世代的な矛盾が放置されていた(重婚クラスタ循環を世代分割していなかった)ためだ.今回はごく一部の不可避多重を除外してすべてのカードの世代が確定しているので,このような多重の発生する余地がない.

image

「婚姻関係で連結したカード集合」を「重婚クラスタ」と呼ぶことにする

これまで「重婚同類」と呼んできた「婚姻関係で連結したカード集合」を「重婚クラスタ」と呼んでみることにした.「セックスつながりのグループ」を俗になんと言っているのか分からないが,「重婚クラスタ」とはまさにそのような関係で連結したグループ以外のなにものでもない.薫のZ木家系図の登場人物は全36名だが,そのうち最大の重婚クラスタには23名ものメンバーが加わっている!重婚クラスタは「同値類」なので,あるクラスタに属するメンバーは他のクラスタとはまったく関わりがない.つまり,クリスプなクラスタである.※

※疫学的なクラスタの定義からはやや離れるが,「コロナ感染者」というグループを一つのクラスタであると考えると,世界中のコロナ感染者は明らかに世界でたった一つのクラスタに属している.コロナ前・コロナ後の世界が変わるというのはこのこと,つまり「世界は一つという事象の顕在化」を意味していると考えられる.

系図図面の縦軸は時間(世代)であり,婚姻関係とはある特殊な時間を共有することであると考えられるので,婚姻関係にあるカードは同世代に属するというのが系図作図上の基本原則である.しかし,世の中には世代差のあるカップルはいくらでも存在する.このようなケースでもその世代差を補助的な垂線(長い尻尾)で補うことによって(2つのカードを同世代に配置することにより)作図可能である.重婚クラスタを節点とし,親子関係を枝とするグラフ(重婚グラフ)を連結成分に分解すると,系図上で多重カードが発生するメカニズムが見えてくる.

重婚クラスタが親子関係によって多段に連結されていたとしても,それだけでは多重カードは発生しない.このような場合にはクラスタを階層的に配置することが可能であり,(同一クラスタに属する)同世代カードはBTWなどの技法によって(連結線を用いることで)単一のカードとして描画できる.多重カード発生の根本原因はこの階層化が循環によって不可能になることにある.(階層=木は閉路を含まない連結グラフである)従って,多重が発生するためには重婚グラフの連結成分が強連結であるか,ないし強連結成分を含むことが必要かつ十分な条件となる.

この条件には一つの重婚クラスタの内部に「親と子」が存在してグラフ的には自己ループを構成する場合を含んでいる.「重婚クラスタ」と言っても,その参加者がすべて「重婚者」であると言う訳ではない.たとえば,薫のZ木家系図のクラスタ23には単婚者が11人も含まれているが,そのうちの一人弘徽殿大后のカードは多重になっている.

重婚グラフは重婚クラスタをノードとし,親子関係を枝とするグラフだが,多重カードの最大の原因が重婚クラスタの内部親子関係であることは事例から明らかだ.ただし,現状では多重枝を登録しようとすると,「多重枝の登録は不可」が表示されて弾かれてしまうので,クラスタ23に関わる自己ループ枝は1つしか登録することができない.このために,重婚クラスタ循環=1ということになっているのだが,これは実態を反映していない.⇒重婚グラフの属性をMULTIEDGEGRAPH(多重枝グラフ)に設定して動作を見ることにした.

薫Z木家系図の重婚グラフには親子枝が23個登録され,このうち10個がクラスタ23に関わる循環枝(自己ループ)であることがわかった.

  1. 【#408 @2桐壷院】⇒【#406 @1光源氏】
  2. 【#408 @2桐壷院】⇒【#488 @42朱雀院】
  3. 【#410 @3桐壷の更衣】⇒【#406 @1光源氏】
  4. 【#488 @42朱雀院】⇒【#514 @55落葉の宮】
  5. 【#488 @42朱雀院】⇒【#518 @57女三宮】
  6. 【#556 @76致仕太政大臣】⇒【#562 @79柏木】
  7. 【#624 @110弘徽殿大后】⇒【#488 @42朱雀院】
  8. 【#656 @126源氏宮】⇒【#518 @57女三宮】
  9. 【#674 @135致仕太政大臣の北の方】⇒【#562 @79柏木】
  10. 【#710 @153一条御息所】⇒【#514 @55落葉の宮】

これはかなり予想外の結果だ.これらの枝をすべて除去しないと,この系図の重婚クラスタ循環は解消しない.そのことを考えると薫のZ木家系図で多重カードが3件しか出ていないというのはむしろ驚異と言ってもよい.薫のZ木家系図を再掲してみよう.

image

おそらく,この図面で多重カード3というのは極限と思われるが,上の循環枝リストからは3という数字を導出することはできない.考えられるファクタとしては,「世代数」が関わっている可能性だ.このクラスタに関わるノードをすべて色表示してみよう.

image

クラスタ23に関わる23のカードは3世代の範囲に分布している.このことから推定されるのは,おそらく「クラスタが分布するk世代の範囲に相当する多重カードkの出現は避けられない」ということではないだろうか?最適解と言えるかどうかは分からないが,各世代に高々1枚の多重カードというのは望み得る限界ではないかと思われる.この循環する親子枝10本を解析すれば,このクラスタを展開するのに少なくとも3世代を要するということを立証できるのではないだろうか?もし,うまくゆけば,メンバーをどのように世代配分すれば最適かというところまで分かるかもしれない… つまり,本来なら一世代に収まらなくてはならない重婚クラスタを「世代分割」可能か?という問題として定式化できる.

それを調べるには,やはり次の段階であるハッセ図と呼ばれる階層図の生成手順を見るしかないだろう.ハッセ図(多重グラフ3)は重婚グラフ(多重グラフ2)をベースに構築される.もし,その手続きの中で「重婚クラスタの(多重を最小化する)世代分割」が可能であれば,図面はほぼ仕上がったものと言ってよい.つまり,(多重不可避のカード以外の)すべてのカードの絶対世代番号が確定できるということになる.もちろん,多重が存在しない場合にはすでに目標は達成されている.問題は重婚クラスタ循環が存在する場合にそれが可能か?という点にある.

重婚同類循環数=重婚同類循環を切断するために削除された枝数とする

重婚同類循環の見直しに掛かっているところだが,先に進む前にまず,最近の修正をフィックスしてから取り掛かることにしよう.仕掛りのオプションには以下がある.すべて現状でフィックスすることにする.

  1. majortribepathを廃止する@20210115 7箇所
  2. 異世代ではBTWは成立しない@20210115 廃止
  3. setMajortribeを使う@20210115 4箇所
  4. MakeTooYoungWifeでSTOPCARDSHIFTを無視@20210116 1箇所
  5. 系列木グラフを連結成分に分解する@20210115 4箇所
  6. DONTSORTTRIBEを廃止する@20210116 廃止
  7. AlternateTribeRealNodeを廃止する@20210116 2箇所
  8. adjustGenerationRangeを停止する@20210117 1箇所 ⇒ 保留
  9. ChangeRealRefferenceで系列優先ノードの切り替えは実施しない@20210119 1箇所
  10. TailSheddingで差分が負の場合はノード対を破棄する@20210120 1箇所
  11. sameGeneCyclesのカウントを整理する@20210124 6箇所
  12. removeDeadEndBranchを廃止する@20210124 3箇所
  13. 重婚グラフの自己ループ枝を削除しない@20210124 6箇所

仮修正6箇所,暫定修正7箇所.

源氏物語全系譜6.1.ZELの傍系血族図を#4 明石中宮で開いて,TestInevitableMultiZeroでエラーが発生する.TestStronglyConnectedで循環が検出されて停止した後,MakeHasseDiagramで無限ループしてスタックオーバーフローが起きる.⇒上記でremoveDeadEndBranchを廃止しているが,この修正は誤っている.removeDeadEndBranchは「行き止まりノードの枝をグラフから削除」して閉路上の枝だけを残すための関数で,同種の関数が三種ある.

①RemoveDeadEndBranch,②removeDeadEndBranch,③RemoveSingleEndBranch,うち①は軸線図グラフ専用で,②はそれを汎用化したもの,③は「一端が一本の入力枝ないし出力枝しか持たないような枝を削除する」ための関数で,最終的には閉路上の枝だけが残るというものだが,②と③を等価な関数と早とちりしてしまった.実際には,③は対象が無向グラフでなくては動作しない.⇒removeDeadEndBranchを復活させ,①をRemoveDeadJikusenBranchにリネームして,RemoveDeadEndBranchの名前は②が引き継ぐようにした.

これで障害は解消したが,TestInevitableMultiZeroでTestStronglyConnectedの戻り値が非ゼロでハッセ図が循環しているというエラーになった.これは削除した枝を戻すとき自己ループ枝しか保留していなかったためだ.重婚同類循環に関わるすべての枝(重婚同類循環を切断するために削除されたすべての枝)を保留するようにしてエラーは解消した.これに伴い,SameGeneMarriageCirculationをBuildSameGeneMarriageGraphに吸収して一本化し,さらに,TestInevitableMultiZeroの一部もBuildSameGeneMarriageGraphに移動して,(1)婚姻/重婚グラフと(2)ハッセ図の境界を明確にした.

sameGeneCycles(重婚同類循環数)の値は「重婚同類循環を切断するために削除された枝数」と再定義する.

▲同上サンプルの全体図を#101 朧月夜で開いて,NAMEBOX:IsPossibleBTWLeftHandで停止する.「右手配偶者が左手本人」というエラーが起きている.エラーを無視して描画は可能.障害ノードはNAMEBOX #1826 髭黒(2)で,右手結婚枠はMARGBOX #1182:#1254 玉鬘(0)+#1373 髭黒(0)→#1266 左兵衛督(0),右手配偶者はNAMEBOX #1373 髭黒(0).髭黒(0)を左手本人とするBTW右手結婚枠はMARGBOX #1206:#1391 髭黒の前の北の方(0)+#1809 髭黒(1)→#1810 真木柱(2).

出力を見ると,髭黒+玉鬘のBTWは成立しているが,髭黒+髭黒の前の北の方では連結線が引かれていない.髭黒と髭黒の前の北の方は同世代なので連結線を引くことは可能であるように見えるのだが… FALSEで復帰するようにすると,髭黒は玉鬘の隣に配偶者として配置され,髭黒の前の北の方とは連結線で結ばれるが,本人カードが別の位置に配置され,他の配偶者(木工の君,中将のお許)との結婚はその位置で展開される.「右手配偶者が左手本人」をエラーとしないという方向でよいと思われるが,ここでは保留としておく.この件は「右手配偶者が左手本人で停止しない@20210108」というDEBUGオプションになっている.

同上サンプルの直系親族図 基準ノード=#49 八宮でNAMEBOX:RestoreExtractBoxのMargPointOffsetエラーが発生した.エラーを無視して描画は可能.MARGBOX::CheckMargBoxChanged→ IfMargboxVisible→ ConfirmGoldenCouple→ GetGoldenCouple→ RestoreErasedYoungWifeが実行されている.MargPointOffsetの移動量は-1なので誤差の範囲と考えられる.

image

完全木テストが完了した.

image

前回より少し速くなっている.だいぶ重婚同類循環の様子が見えてきた.前出のサンプル:薫のZ木家系図の場合で言うと,重婚同類循環は1つで,これに関わっているノードは23点ある.

  1. Corder=3 wives=13 #406 光源氏 @1
  2. Corder=3 wives=6 #408 桐壷院 @2
  3. Corder=3 wives=1 #410 桐壷の更衣 @3
  4. Corder=3 wives=5 #488 朱雀院 @42
  5. Corder=3 wives=2 #514 落葉の宮 @55
  6. Corder=3 wives=2 #518 女三宮 @57
  7. Corder=3 wives=5 #556 致仕太政大臣 @76
  8. Corder=3 wives=2 #562 柏木 @79
  9. Corder=3 wives=2 #600 夕顔 @98
  10. Corder=3 wives=2 #604 源典侍 @100
  11. Corder=3 wives=2 #606 朧月夜 @101
  12. Corder=3 wives=2 #612 藤壷の宮 @104
  13. Corder=3 wives=1 #624 弘徽殿大后 @110
  14. Corder=3 wives=1 #628 麗景殿女御 (桐壷院の)@112
  15. Corder=3 wives=1 #644 八宮の母女御 @120
  16. Corder=3 wives=1 #656 源氏宮 @126
  17. Corder=3 wives=1 #670 麗景殿女御 (朱雀院の)@133
  18. Corder=3 wives=1 #674 致仕太政大臣の北の方 @135
  19. Corder=3 wives=1 #708 承香殿女御 (朱雀院の)@152
  20. Corder=3 wives=1 #710 一条御息所 @153
  21. Corder=3 wives=2 #752 雲井の雁の母 @174
  22. Corder=3 wives=1 #838 承香殿女御 (桐壷院の)@217
  23. Corder=3 wives=1 #856 近江の君の母 @226

婚歴を見ると,光源氏13, 桐壷院6, 朱雀院5, 致仕太政大臣5を筆頭に,落葉の宮,女三宮,柏木,夕顔,源典侍,朧月夜,藤壷の宮,雲井の雁の母の2が並んでいる.しかし,これだけでは循環にはならない.これに,桐壷院→光源氏,弘徽殿大后→朱雀院,致仕太政大臣→柏木などの親子関係が(自己ループとして)加わってようやく重婚同類循環が完成する.「重婚同類」と呼んでいるのは,「重婚関係」で連結したグループが排他的な「同値類」を構成するためだ.今後は重婚同類と言う代わりに「重婚クラスター」と呼んでみることにしよう.

「重婚同類循環は1つ」という言い方をしているが,この表現はあまり正確ではない.重婚グラフから抽出した強連結成分を分解するのに必要な最小枝数を重婚同類循環数としているが,現在扱っている枝グラフは多重枝を認めていないため,自己ループが1個しか登録されていないことで最小枝数1ということになっているので,多重枝を扱えるようになると,たとえば,今のサンプルの場合なら少なくとも自己ループ枝は3つになるはずだ.従って,「重婚同類循環の個数」と「重婚同類循環を分解する最小枝数」ははっきりと区別する必要がある.

多重カード出現の極小化

現行ではすでに垂直セグメント検定(TestVerticalSegment)という処理は廃止(2018/02/26)されているので,垂直スプリットが発生したときそれを補正する手段は存在しないが,AFTERMARGSAMEGENEフェーズでShiftDirectAbsoluteの実行を抑制すれば垂直スプリットの発生を回避することはできる.これは重婚同類検定ですべてのノードの垂直位置(世代)を事前に決定しているためと考えられる.しかし,循環性多重が存在するときには重婚同類検定の結果をまったく使えないというのも過剰であるように思われる.重婚同類検定をもう少し深堀りして,「寸止め」のところまでは実行できるようにしたい.

目的は「多重カード出現の極小化」にある.実際,昨日の反例サンプルを見ると,少なくとも2枚の多重カードは削減できるように思われる.多重カードの削減はメインループの中でも動的にTOPOLOGY:ReduceMultiCardを使って実施しているはずなのだが… なぜそれが利かないのか調べてみたところ,多重カード数が不可避の多重カード数と同じになるとブレークしていることがわかった.この「不可避の多重カード数」を強制的に-2してループするようにして,下図を得た.

image

多重カード数は5から3に減少している.重婚同類循環の件数は3なので明らかにこの図面がこれ以上多重カードを削減できない極限に達していることは明らかだ.つまり,この図面は少なくともこのサンプルに限って言えば,「完成図」であると言える.TribeRelocationのステージ【7.8】多重カードゼロを検査して変化なしなら検定打ち切りの打ち切り条件を多重不可避(Inevitables)から重婚同類循環(sameGeneCycles)に変えることでこの動作を一般化できる.しかし,これだけでは多重を極小化することはできない.

たとえば,光源氏の全体図では重婚同類=3であるにも関わらず,多重カードは16も残ってしまう.このとき,多重不可避は8となっている.まず,これらの用語の意味をもう一度確認しておこう.重婚同類循環(sameGeneCycles)はTestInevitableMultiZeroが返す値で,重婚同類循環の発生件数と考えられるが,かなりルーズな扱いを受けているのでもう少し整理する必要がある.sameGeneCyclesは以下のポイントでインクリメントないしセット/リセットされている.

  1. TOPOLOGY::ResetExperiment 初期化
  2. TRIBERELOCATIONフェーズ入口 リセット
  3. SIMPLENODE::MakeOyakoEda(多重グラフ2) 
    自己ループでカウントアップ
  4. BuildSameGeneMarriageGraph(多重グラフ2)の結果を加算
  5. SameGeneMarriageCirculationの結果を加算
  6. TOPOLOGY::TestInevitableMultiZero 
    除去したループ枝の個数をカウントアップ
  7. TestInevitableMultiZeroの結果をセット

どうもあまりわかり易くない.重婚同類検定では枝グラフを3つ使っているのだが,それぞれの構成/用途を確認しておこう.

  1. 多重グラフ1(tajugraph1) 節点:人名カード,枝:婚姻関係(単身婚の場合は自己ループ),複数親を持ち子どもを持たない終端ノードの自己ループ枝,連結成分:婚姻関係によって連結するカード集合
  2. 多重グラフ2(tajugraph2) 節点:重婚同類(グラフ1の連結成分),枝:親子関係(親の含まれる連結成分と子の含まれる連結成分を結ぶ)自己ループを削除しsameGeneCyclesに加算する,冗長枝を除去する 連結成分:親子関係によって連結する重婚同類の集合(重婚同類族)
  3. 多重グラフ3(tajugraph3) 節点:グラフ2の連結成分,枝:親子関係(グラフ2の枝)

これもまた,あまりわかり易いものではない.特に多重グラフ3と2の違いがよく分からない.グラフ3の枝がグラフ2の親子関係であるとすれば,すべての枝が自己ループになってしまうような気がするのだが… ここではグラフ3 をハッセ図と呼んでいるようだが,その意味も不明だ. SIMPLEGRAPH:TightenHasseDiagram では出口でグラフ2の除去された枝をすべて元に戻している.BuildTightHasseDiagramでは静定するまでこの関数を呼び出しているのだが…

しかし,この部分を読み解かないことには始まらない.グラフ1を婚姻グラフ,グラフ2を重婚グラフ,グラフ3をハッセ図と呼んでおくことにしよう.1の婚姻グラフに2種の自己ループ枝が追加されている理由が分からない.重婚同類を見るためには婚姻関係と親子関係の両方を見る必要はあるが,なぜ単身婚を加える必要があるのか?世代を確定するためには必要になってくるという可能性も考えられるが…

2の重婚グラフの目的は重婚同類循環(sameGeneCycle)の個数を確定することにあると考えてよいだろう.しかし,カウントを持ち寄って合算するような判り難いものになっている.3のハッセ図の目的は多重を除去した状態で絶対世代番号を確定しようとしているものと解釈されるが,毎回枝を除去してまた戻すという操作の意味がよく分からない.

ハッセ図はとりあえず置いて,重婚同類循環(sameGeneCycle)を確定することを考えよう.重婚グラフのノードである重婚同類が親子関係としての自己ループを持つとすれば,重婚同類循環に当たることは明らかだが,親子関係によって連結した連結成分要素である重婚同類族それ自体はただちには重婚同類循環には当たらない.それをどこで見ているのかをチェックしておこう.

最初にsameGeneCyclesをカウントアップしているのは,BuildSameGeneMarriageGraphだが,ここの処理で分かりづらいのは,この関数の内部ですでにsameGeneCyclesを一部カウントアップしているという点だ.これは改めるべきだろう.この関数はカウンタloopedgeの値を返しているが,AddOyakoEdaの中ですでにsameGeneCyclesをインクリメントしている.SIMPLENODE:AddOyakoEda→MakeOyakoEdaでは自己ループになったときにはその枝のselfloopフラグをオンにしている.

BuildSameGeneMarriageGraphではこの後,自己ループ枝を削除するときにloopedgeをインクリメントしているので,重複カウントされている可能性がある.⇒この修正で重婚同類個数は2になったが,多重カード3が残る.メインループはトポロジーが変化しなくなると多重が残っていてもループを抜けるようになっている.見たところいずれも「不可避の多重」のように見える.重婚同類循環が2つであるとすれば,その構成要素を特定できなくてはならない.重婚同類循環の検査はSameGeneMarriageCirculationで実施している.しかし,この検査では循環は検出されていない.SameGeneMarriageCirculationではTestStronglyConnectedでグラフを「強連結成分に分解」している.

サンプルとして用いている薫のZ木家系図では重婚同類循環数は2となっている.一つは自己ループ(桐壷院→光源氏)だが,もうひとつはなんだろう?TestStronglyConnectedは3箇所で使われている.一つはSameGeneMarriageCirculationの中であとの2箇所はTOPOLOGY:TestInevitableMultiZeroだが,いずれも無反応だ.ただし,TestInevitableMultiZeroではtajugraph2->selfloopの値をsameGeneCyclesに追加している.これはおそらく前にカウントした値が残留したものと推定されるので,おそらくカウントの重複と思われる.selfloopcountで数え直ししてみることにしよう.

自己ループが1残っている.ただし,これはすでに削除された枝だ.削除と言っても現物はそのまま,「削除された」というマークが付いているというだけだが,selfloopcountはマークを無視してカウントしている.このルーチンを修正して,自己ループカウントはゼロになったが,今度はTestInevitableMultiZeroのステージ【14】強連結成分の抽出で削除した枝を戻すの段でカウント不一致が発生するようになってしまった.この関数は元の仕様に戻した方がよさそうだ.その代わり,sameGeneCycleにloopedgeを加算ではなく,代入とするのが正しい.

結局,重婚同類循環=1で多重カード3という結果になった.しかし,これでもまだあいまいさが残る.重婚グラフの枝集合に含まれる自己ループは重婚同類循環に関わるものだが,必ずしも一対一という訳ではない.自己ループ以外の循環はSIMPLEGRAPH::TestStronglyConnectedで検査しているが,この検査では「行き止まりノードの枝をグラフから削除する」ということを実施しているだけで,もし枝が残ればそれは重婚同類循環に含まれる枝ということになるが,この場合はサイクルを構成する枝集合と重婚同類循環が対応するということになり,やはり一対一の対応にはなっていない.整理すると以下のようなことになる.

  1. 重婚グラフの枝集合の中の自己ループ枝は重婚同類循環に関係する
  2. TestStronglyConnectedで除去されなかった枝は同類循環に関係する

ということになり,重婚グラフの枝集合を見ても確定的な情報は得られない.連結成分はノードの集合であり,それらがどの枝に関わっているかということは直ちには分明ではない.TestInevitableMultiZeroの出口で重婚グラフをダンプすると,節点数=6 枝数=7 有効枝=6 removed=1 nouse=0 usable=0 selfloopcount=1という結果になった.接点数6というのは重婚同類が6個あることを意味する.枝数7でうち有効枝数6というのはremovdeが1あるためだろう.selfloopcount=1というのは枝リストをスキャンして得た値だろう.7つの枝のうち,removedとselfloopのものが1つある.

SIMPLEEDGEには,selfloop,removedの他に,usableというメンバー変数がある.これには閉路上の枝というコメントが付いているので,これを利用することにしよう.いや,この変数は「有効枝」という意味で使われているようだ.今の場合はこれと真逆な使い方になる.「冗長枝」という意味で使われることもあるようだ.この他にnouseという変数もある.これには「切断された枝」というコメントが付いている.removedとどういう違いがあるのだろう?nouseというのは連結成分への分解で使われているようだ.

SIMPLEEDGEにcircular // 循環する枝(閉路上の枝)という変数を追加した.また,SIMPLEGRAPHにcirculars // 循環する枝数(閉路上の枝,自己ループを含む)を追加した.

InvestigateVSplits 垂直スプリット発生

源氏物語全系譜6.1.ZELのZ木家系図 基準ノード=#74 薫の出口検査でTREEVIEW::InvestigateVSplits 垂直スプリット発生

image

以前は「垂直セグメント検定」というのが実装されていたのだが,現在は完全に廃止されていて,それに代わるものは存在しない.20.AFTERMARGSAMEGENEで多重が発生しているとき,ShiftDirectAbsoluteとAdjustTribeGenerationを実行しないようにすればこの障害は回避できる.

image

この設定で源氏の完全木テストを実施してみた.

image

これまででもっとも速い時間で終わった.しかし,これで終わりという訳にはゆかない.この図面には多重が5件発生しているが,もう少し削減できるはずだ.その解を求めるためにはもう少し重婚同類検定を深堀りする必要がある.つまり,循環性多重が存在する場合でもShiftDirectAbsoluteを実行するという方向を追求してみたい.

ダミー枠チェーン上の結婚枠直列検査

源氏物語全系譜6.1.ZELの傍系親族図を#8 夕霧で開いて,MARGBOX:GetUpperNodeで停止する.TYW枠 #2396:#2438 弘徽殿女御(3)+→#1704 弘徽殿女御(1)の直上に親ノードの#2438 弘徽殿女御(3)が存在しないという状態になっている.これは,#2347 紅梅(1)でTailSheddingを実行したとき,紅梅のTYW枠#2348:#2541 紅梅(2)+→#1328 紅梅(0)を一旦取り外す操作に弘徽殿女御のスレッドを巻き込んでしまっているためと推定される.

TailSheddingでは,長い尻尾の末尾に接続するTYW枠を対象に取り出しを実施しているが,このケースのように複数のTWY枠が接続していることを想定していない.⇒いや,スレッド自体は区別されている.長い尻尾は独自のldrchainによって独立したチェーンを構成しているから,取り出そうとしているTYW枠自体は間違っていない.youngwife->CutBranch(false)の操作で誤っているのではないだろうか?⇒いや,CutBranchではまだ誤操作されていない.その次のyoungwife->Disconnectではないか?いや,ここでも問題は生じていない.むしろ,TYW枠をつなぎ戻すところでやり損なっているのではないだろうか?MakeLongTailが元凶であるように思われる.

NAMEBOX::MakeLongTail→ makeLongTailは「LDR束代表ノードが所有するLDR束のデータ整合性をチェックする」という関数だ.makeLongTailの中で不良が検出されている.不良スレッド thread=NAMEBOX #2395 弘徽殿女御(2) この処理ではMakeLongTailの戻り値はチェックされていないが,現在の不具合と関係がある可能性はある.⇒暫定的にエラーで停止するようにしておいたが,このエラーは座標に関するもので現在の問題とは直接関わっていないような気がする.問題はMakeLongTailではTYW枠の存在がほとんど無視されているように思われる点だ.不良はmakeLongTail→ CheckDummyBox→ CheckDummyBoxSerialで起きている.

この関数は「代表LDRノードのダミー枠チェーン上のすべてのダミー枠につきダミー枠直列を検査」している.ダミー枠直列というのは,結婚枠がYリスト上で直列に並んでしまう事象を指すものと思われる.これは拡張子ども枠などの場合に起こり得る現象だ.「ダミー枠直列」として,NAMEBOX #2347 紅梅(1) のスレッドで,MARGBOX #2435:#2347 紅梅(1)+→#2438 弘徽殿女御(3)とMARGBOX #2348:#2347 紅梅(1)+→#1328 紅梅(0)が直列であることが検出され,その結婚枠がTYW枠である場合には,それをYリストから切り出した後,長い尻尾の末尾に繋ぎ変えている.

image

紅梅(1)はLDR束の代表ノードでdummyboxにはMARGBOX #2435:#2347 紅梅(1)+→#2438 弘徽殿女御(3)が入っている.このダミー枠にはNAMEBOX #2438 弘徽殿女御(3)しか入っていない.従って,紅梅(1)の長い尻尾終端は弘徽殿女御(3)ということになり,TYW枠MARGBOX #2348:#2347 紅梅(1)+→#1328 紅梅(0)はこのノードの後ろに挿入されるため,#2396 MARGBOX→ #2348MARGBOX→ #2436 NAMEBOXという並びになってしまう.

MARGBOX #2435とMARGBOX #2348が直列になるというのは確かにおかしいが,今の場合#2348は紅梅(1)に直結されなくてはならないはずだ.しかし,TYW枠というのはダミー枠には勘定されないから,MARGBOX #2348の居場所がないということになる.これは想定外の構図なのだろうか?紅梅(2)が削除される前の状態をもう一度チェックしてみよう.最初の図式では紅梅(2)と弘徽殿女御(3)はともに終端ノードなのでその先にTYWをぶら下げても矛盾は生じないが,親元結婚枠内のLDRに直結するTYW枠の場合,ダミー枠と直列構造になることは避けられない.問題はこのときの移動先が間違っているということではないだろうか?ダミーノードはldrchainでリンクされているが,LDRからダミーノードへのリンクがかなりあいまいになっているような気がする.

LDR→ TYW枠はtooyoungで参照できるが,LDR→ダミーノードへの参照が存在しない.これは世代差ゼロのTYW枠,つまり,ZTYW枠とはまた別の話だ.⇒いや,少し誤解している.LDRもldrchainを使っている.TYW枠を持つLDRのldrchainが空であるということは,そのノードが長い尻尾の末尾であることを示している.このノードが代表ノードであるときには,ダミー枠直列というのはノーマルと考えてよいはずだ.⇒CheckDummyBoxSerialで直列枠がTYW枠のときは,GetTYWElderでELDERWIFEを取り出し,longtailの末尾を取り出すようにした.

同上サンプルの傍系親族図を#151 左大臣の姫君でソートして,NAMEBOX::makePairBoxで停止した.NOCOMMON属性未設定がPAIRBOX #2049:#2046 玉鬘(2)→#1254(0)で起きている.対象ノードはNAMEBOX #1596 秋好中宮(1).このエラーを補正するにはPAIRBOX::CheckNoCommonEndPointを実行しなくてはならないが,CheckNoCommonEndPointはNAMEBOX::makePairBoxでノード対生成時に一度チェックされる他は,PAIRLIST::RepairPairBoxが回ってこないと補正されない.従って,秋好中宮(1)のノード対生成時にこの事実が判明していないのは避けられない.

いや,端点共有があるときは,SwapBundledPairが実行されている.この中で検査していないということは考え辛い.⇒SwapBundledPairの中でノード対のNOCOMMONPAIR属性をチェックするのではなく,直接CheckNoCommonEndPointを実行するようにした.

同上サンプルの法定親族図を#104 藤壷の宮でソートして,PAIRBOX:RepairCommonEndPointで停止した.端点一致でsamepointゼロエラーが,PAIRBOX #2072:#2067 弘徽殿女御(2)→#1329(0)で起きている.対象ノード対はPAIRBOX #2072:#2067 弘徽殿女御(2)→#1329(0).このノード対は「端点一致でsamepointゼロ」という理由でRepairCommonEndPointを実行しているのに,状態が変化していない.

RepairPairBoxでチャンネルの付け替えが実施され,チャンネル0→2に移動している.このあと,MoveSamePointでチャンネル0への移動が実施されるが,この関数の中で「最大区間でないnocommonを別チャンネルに移動」が発生し,PAIRBOX #2028:#2025 玉鬘(2)→#1254(0)が別チャンネルに移動している.このとき,弘徽殿女御のノード対も一緒に移動しているものと見られる.

MoveSamePointでSwapBundledPairを実行するまではsamepointは値を持っているが,最大区間が逆転しているため切り離されて元の状態に戻っている.これは,端点共有と認定しているところで間違っていると考えられる.少なくとも共有端点ノード対がNOCOMMONであるか否か,もし,NOCOMMONならノード対の区間と比較する必要がある.

PAIRBOX::searchCommonPairが誤認定していると見るべきではないか?⇒Bobject::getCommonPairTypeではNOCOMMONPAIRクリティカル属性を見るのではなく,直接CheckNoCommonEndPointを実行するようにした.また,getCommonPairTypeの呼び出し側では,実ノードのNoCommonGoodSonを実行するか,ないしCheckNoCommonEndPointを実行するようにした.

image

▲同上サンプルのZ木家系図 基準ノード=#74 薫の出口検査でTREEVIEW::InvestigateVSplits 垂直スプリット発生

TailSheddingで差分が負のときはノード対を破棄する

源氏物語全系譜6.1.ZELの直系親族図を#57 女三宮でソートして,TRIBELIST::ShiftDirectAbsolute→ NAMEBOX:TailSheddingで停止.TailSheddingはトカゲの尻尾切り(tail shedding)のように長い尻尾を切り詰める操作だ.仮ノードと実ノードの世代差をD,長い尻尾の長さをLとしてdiff = L-Dだけ短縮されるが,diffが負の場合は操作できないためエラーになる.ShiftDirectAbsoluteではカードを絶対世代番号で指定された位置にシフトして重婚同類検定の結果を図面に反映させようとしているが,重婚同類循環が発生している場合には絶対世代番号を一意に決定することができないため,矛盾が生じているものと考えられる.

従来仕様では重婚同類循環が検出された場合にはShiftDirectAbsoluteを実行しないようになっていたのだが,可能な限り適用するというのが現在の方針だ.,まず,diffが負のときには無動作で抜けるように修正してみよう.⇒停止しないで描画できるようになった.このような事象は光源氏(2)と朱雀院(1)で起きている.TailSheddingでは差分がゼロのときには「LDRノード対の世代差がゼロになったときは仮ノード消去に転換する」という処理が組み込まれている.

また,TOPOLOGY::CheckShiftedPairBoxでは差分が負の場合はノード対を破棄するという処理を実行しているので,この論理をTailShedding本体に組み入れてみよう.⇒出力はかなり変わった.光源氏(0)と朱雀院(0)の頭に入っていた連結線が消え,桐壷の更衣→光源氏,弘徽殿大后→朱雀院が直結するようになった.

image

図面的にはこの方がわかり易い.修正前の図面を出してみよう.

image

コンポーネントは同一で,それらが水平シフトしているだけの違いだが,(修正後の方が)変に込み入ったところがないのがよいと思う.

同上サンプルの傍系親族図を#6 玉鬘でソートしてTRIBEBOX:SetMinorTribeで停止した.系列優先実ノードと優先仮ノードの実ノードが異なるというエラーだ.⇒NAMEBOX:DoublyBlessedOneでOnDoublyBlessedOneである区間を延長して,ChangeTribeRealnodeを実行しているブロックを含むようにした.また,これと付随して,hasPhysicalConnectionでこれまで優先仮ノードが(PRIMGHOST | ELDERWIFE))属性を持つ場合はつねに物理コネクション成立としていたのを,「実ノード不一致」と「実ノード属性不正」では不成立とした.

▲同上サンプルの傍系親族図を#8 夕霧でソートしてMARGBOX:GetUpperNodeで停止した.MakePairListClean実行中,CheckPassingPoint→ NoCommonGoodSon→ GetUpperNodeで起きている.障害が起きているのはMARGBOX #2396:#2438 弘徽殿女御(3)+→#1704 弘徽殿女御(1)で,TOOYOUNGWIFE|MOTHERLESS属性とHASNOMARGLINKクリティカル属性を持っている.

HASNOMARGLINKは「結婚リンクに接続していない結婚枠」の意味で,#2438 弘徽殿女御(3)に接続している.しかし,この結婚枠の上流には弘徽殿女御(3)は出てこない.このパスは#1324 致仕太政大臣(0)まで続いているが,弘徽殿女御(3)はその経路上に現れない.

この結婚枠の上にはMARGBOX #2348:#2541 紅梅(2)+→#1328 紅梅(0)があり,その上のノードはNAMEBOX #2541 紅梅(2)だ.MARGBOX #2348もMARGBOX #2396とまったく同じ属性を持っているのでTYW枠と考えられる.紅梅(2)はダミーノードでその上にMARGBOX #2435:#1700 柏木(2)+→#2541 紅梅(2)があり,その上がNAMEBOX #2347 紅梅(1) でEXTRACTBOX|ELDERWIFEになる.#2438 弘徽殿女御(3)の長い尻尾をDumpLongTail で見ると,

NAMEBOX::DumpLongTail >>>>>>>>>> #2438 弘徽殿女御(3)
   1  dum#=1 #1700 柏木(2)
   1  dum#=2 #2347 紅梅(1)
   2  dum#=2 #2541 紅梅(2)
   1  dum#=3 #2395 弘徽殿女御(2)
   2  dum#=3 #2438 弘徽殿女御(3)
NAMEBOX::DumpDummyChain >>>>>>>>>>>
   1  thread #1700 柏木(2) #2347 紅梅(1) #2395 弘徽殿女御(2)
   2  #2435 [結婚枠]:[4] #2541 紅梅(2) #2438 弘徽殿女御(3)
NAMEBOX::DumpLongTail <<<<<<<<<<<<

のような3本のスレッドからなるLDR束になっている.#2541 紅梅(2) と#2438 弘徽殿女御(3)は一つのダミー枠に入っているので,その下位のMARGBOX #2396が上位の#2541 紅梅(2)を参照しているというのはノーマルなのではないだろうか?#2438 弘徽殿女御(3)はDUMMYBOX|BUNDLEDSTRINGという属性を持っている.この結婚枠のownerは弘徽殿女御(0)だ.

障害はNAMEBOX::TailSheddingで発生している.NAMEBOX #2347 紅梅(1)の長い尻尾の始末を付ける段で,同じLDR束に入っている#2438 弘徽殿女御(3)が影響を受けているものと見られる.処理開始前の状態は下記のようになっている.

NAMEBOX::DumpLongTail NAMEBOX::TailShedding >>>>>>>>>> #2347 紅梅(1)
   1  dum#=1 #1700 柏木(2)
   1  dum#=2 #2347 紅梅(1)
   1  dum#=3 #2395 弘徽殿女御(2)
   2  dum#=3 #2438 弘徽殿女御(3)
NAMEBOX::DumpDummyChain NAMEBOX::TailShedding >>>>>
   1  thread #1700 柏木(2) #2347 紅梅(1) #2395 弘徽殿女御(2)
   2  #2435 [結婚枠]:[4] #2438 弘徽殿女御(3)
NAMEBOX::DumpLongTail NAMEBOX::TailShedding <<<<<

TailShedding実施後は下記のようになっている.

NAMEBOX::DumpLongTail NAMEBOX::TailShedding >>>>> #2347 紅梅(1)
   1  dum#=1 #1700 柏木(2)
   1  dum#=2 #2347 紅梅(1)
   2  dum#=2 #2541 紅梅(2)
   1  dum#=3 #2395 弘徽殿女御(2)
   2  dum#=3 #2438 弘徽殿女御(3)
NAMEBOX::DumpDummyChain NAMEBOX::TailShedding >>>>>>
   1  thread #1700 柏木(2) #2347 紅梅(1) #2395 弘徽殿女御(2)
   2  #2435 [結婚枠]:[4] #2541 紅梅(2) #2438 弘徽殿女御(3)

結局,紅梅の長い尻尾が1伸びて,#2435のダミー枠に#2541 紅梅(2)が追加されたということになる.この煽りで,MARGBOX #2396が紅梅(2)に直結するようになってしまったのだろう.それでは,紅梅のTYW枠はどこに接続しているのだろう?紅梅のTYW枠#2348:#2541 紅梅(2)+→#1328 紅梅(0)は紅梅(2)を直接参照している.これに対し,弘徽殿女御のTYW枠は「紅梅のTYW枠#2348」を参照している.明らかにこれは誤っていると考えられる.

TailShedding では処理を開始する前に対象スレッドのTYW枠を一旦外して安全な場所に移動した後,スレッドの伸縮を実施して,その後またスレッドの末尾につなぎ戻す操作を行っている.このとき,弘徽殿のTYW枠を共連れにしているような気がする.⇒TailShedding に入ってきたときの紅梅のTYW枠の位置がおかしい.最初から#2438 弘徽殿女御(3) と弘徽殿のTYW枠#2396の間に入っている.CutBranch(false)では下流系を連れて出るので,弘徽殿のTYW枠も一緒に移動してしまう.

最初の構図では確かにGetUpperNodeでは弘徽殿女御(3)をキャッチできるのだが,その後紅梅(2)が入って,紅梅のTYW枠がそれに連結されるため,弘徽殿女御(3)に到達できなくなってしまう.この不良はNAMEBOX #2347 紅梅(1)のTailShedding で起きている.入口では,

NAMEBOX::DumpLongTail NAMEBOX::TailShedding >>>>> #2347 紅梅(1)
   1  dum#=1 #2347 紅梅(1)
   2  dum#=1 #2434 紅梅(2)
   1  dum#=2 #2395 弘徽殿女御(2)
   2  dum#=2 #2438 弘徽殿女御(3)
NAMEBOX::DumpDummyChain NAMEBOX::TailShedding >>>>>
   1  thread #2347 紅梅(1) #2395 弘徽殿女御(2)
   2  #2435 [結婚枠]:[5] #2434 紅梅(2) #2438 弘徽殿女御(3)

のようになっているが,出口では

NAMEBOX::DumpLongTail NAMEBOX::TailShedding >>>>> #2347 紅梅(1)
   1  dum#=1 #2347 紅梅(1)
   1  dum#=2 #2395 弘徽殿女御(2)
   2  dum#=2 #2438 弘徽殿女御(3)
NAMEBOX::DumpDummyChain NAMEBOX::TailShedding >>>>
   1  thread #2347 紅梅(1) #2395 弘徽殿女御(2)
   2  #2435 [結婚枠]:[5] #2438 弘徽殿女御(3)

のように変化している.このとき,紅梅のTYW枠#2348のownerは弘徽殿女御(3)に変わり,Yリストもそのように変化している.これは明らかに誤りと思われる.