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

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

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

系図作図上の要点は多重カードの出現を極小化することにある.カード多重の原因が重婚クラスタ循環の存在にあることは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 // 循環する枝数(閉路上の枝,自己ループを含む)を追加した.