MargBoxTransferで軸線枠を移動する

ZTシステム構成図7.ZELの全体図を#30 MDBで開いてMARGBOX:GetUpperNodeで停止する.「親ノード不在」が起きている.⇒TOPOLOGY::CallBuildShaftLineで軸線枠を設定する際に,結婚枠の親参照パスを付け替えて上位軸線ノードを参照するように修正してエラーは解消したが,絶対座標変換後のCheckAtypicalMarriage(非類型的結婚処理)でエラーになる.

上記サンプルの全体図 #30 MDBでTOPOLOGY:CheckAtypicalMarriageの「stave異常値」エラーが起きる.これは結婚連結線の割当てに失敗したことを意味する.障害が起きているのは,NAMEBOX #772 MDB(0),NAMEBOX #1261 MDB(2)とNAMEBOX #1229 MARGTABLE(1)だ.MDBは親を4組持っている.①topology+TOPOLOGY,②linktable,③namesort+NAMESORT,namesortだ.軸線はnamesortになっているが,topologyという選択もあったはずだ.

この図面では多重が2件発生している.MDBとMARGTABLEだ.topology→MDBは子どもがいないので消去できるはずなのだが,多重になっている.MARGTABLEには親はいない.単身婚の子どもBASETABLEとMDBとの結婚の子どもがいる.これらの子どもはMDB(2)の下にいるが,MARGTABLEとMDB(2)は連結されていない.MDB(0)というカードが可視で残ってしまっていることが混乱の原因なのではないかという気がする.

CallBuildShaftLineでは軸線枠の移動が5件発生している.①MARGBOX #689:#798 COUPLING(0)+#1212 coupling(1)→#747 pagesetup(0),②MARGBOX #655:#745 familytree(0)+#1218 FAMILYTREE(1)→#754 BaseLink(0),③mbox=MARGBOX #704:#772 MDB(0)+#1229 MARGTABLE(1)→#908 anod[]配列(0),④MARGBOX #683:#867 配列要素(0)+#1230 MARGLINK(1)→#1231 nodule(3),⑤MARGBOX #715:#880 margbox(0)+#1232 MARGBOX(1)→#882 owner(0).

これらはすべて有配偶者婚だが,stave異常値に関係しているのは,③のMDB(0)+MARGTABLE(1)のように思われる.出力図面で見ると,結婚枠は移動しているが,MARGTABLE(1)の隣接位置に残ったままになっている.明らかに結婚の移動と同時に配偶者も移動する必要がある.MARGBOX::MargBoxTransferという関数があるので,これを使ってみよう.⇒解決した.

image

ただし,この図面にはまだいくつか疑問が残っている.まず,重婚同類1,重婚1というダンプが出ているが,それらしきものが見当たらない.⇒いや,あった.COUPLINGだ.COUPLING はクラス名だが,クラス名はクラスの(代表)インスタンスと婚姻関係を結ぶことになっている.COUPLINGの孫にextraslot2というCOUPLINGのインスタンスがあるためだ.従って,この図面では多重は不可避であり,重婚同類1というのは正しい.

MDBはオブジェクトであり,すべてのオブジェクトはnoduleないしNODULEから派生しているはずなのに,軸線が届いていないのはなぜか?実際,上図でも右下コーナーに2枚のカードがブルー表示されている.これは養親子系血族であることを示しているのだが… 軸線の選び方が悪いのか,あるいは単なるデータの不備か?軸線の終端ノードの_goodson は子どもがいないのでここで止まるのは仕方ないが,別の経路があったのではないか?

MDBの孫の配列要素はnoduleという子どもを持っているが,最長鎖の長さでは同じになってしまうために選択されなかったのだろう.noduleは「クラス」なので,親はすべてクラスかないしクラス+オブジェクトだ.MDBの軸線の下流系はすべてオブジェクトばかりでnoduleに接続していない.MDBの軸線上のノードは先祖ノードのCOUPLINGを除くとすべてオブジェクトだ.先祖ノードをcouplingに取れば首尾一貫するのだが… 多分父系/母系を指定できるようになればある程度選択可能になるとは思われる…

上記サンプルを#1 couplingでソートしてMARGBOX:setownerで停止した.結婚枠とownerの世代が整合しないというエラーだ.⇒軸線グラフ検定は重婚クラスタ検定より前に実施されるため,まだ絶対世代番号は確定していない.この段階では世代の不整合は見過ごしてもよいのではないだろうか?⇒エラーを無視して描画は可能.MARGBOX::setownerで出る「結婚枠世代不一致」以外にも,「兄弟世代番号不一致」がMARGBOX:checkVerticalPositionで多数発生する.

checkVerticalPositionでは検査の直前に結婚枠と兄弟ノードの世代番号を一致させる処理を実行しているのだが… このエラーの原因はネストした処理の中で系列ポテンシャルが更新されているためだ.本来CheckVerticalPosition中は「系列優先実仮ノード世代差の補正はここでは行なわない」としているのだが,穴が空いているところが(複数箇所)あった.

「CheckVerticalPosition中は系列ポテンシャルの変更不可@20210212」としてエラーは解消した.MARGBOX:CheckVerticalPositionの出口ではSAMEGENEMARRIAGEフェーズでのみTRIBEBOX::adjustGenerationRangeを実行しないとしていたが,これに加えてAFTERMARGSAMEGENEフェーズの場合も同様に抑制するようにした.

image

一応描画はできるようになったが,2つ問題がある.①画面下部で垂直スプリットが発生している,②軸線が通っていない.まず,①の問題から見てゆくことにしよう.絶対世代番号はLIST[8]→ DATALIST[9]→ nodule[10]となっているが,実際にはLIST[8]→ [9]→ DATALIST[10]→ [11]→ [12]→ nodule(3)[13]のように伸びてしまっている.

noduleは(0)と(3)で多重になっているが,世代的には(0)の方が正しい.LISTとDATALISTには同名で♂型と♀型の2種があるので混乱するが,リネームしないでこのまま進めよう.まず,DATALIST♂がなぜシフトされているのかを見ておこう.重婚クラスタ図を出力すると,下図を得る.

image

これは重婚グラフと呼ばれるもので,ShiftDirectAbsoluteでは婚姻グラフの連結成分リストを基準に世代割当てを実施している.上図で各カードの右肩に付いている数字は,グラフ節点の「距離数」と「絶対世代番号」だ.距離数はすでに正規化しているので,世代番号と一致している.この値は,婚姻グラフの連結成分リストの要素(重婚クラスタ)にも転記されているはずだ.というか,重婚グラフの節点は婚姻グラフの連結成分に他ならないので,当然この値が設定されていると考えてよい.

UQモバイルをスマホプランRからくりこしプランMに切り替えた

UQモバイルの契約を変更して,スマホプランRからくりこしプランMに切り替えた.プランRが10GB 2980円/月に対し,プランMは2480円で15GBまで使える上,使わなかった分は翌月に繰り越せるというのだから断然有利だ.容量オーバーになったときの最大転送速度はどちらも1Mbpsで,このスピードがあれば通常のブラウジングやメールの送受信には不自由しない.UQモバイルには「節約モード」というのがあるので,普段は1Mbpsでやりくりし,サイトへのアップロードや動画にアクセスするときだけ「高速モード」を使うことにする.楽天モバイルも熊谷までは来ているが,ここまで届くようになるのはいつのことやら…

ZTシステム構成図7.ZELをMDB @30で開いて,MARGBOX:CheckMarchainで「上位人名ノード不整合」のエラーが出た.

軸線グラフを生成するときの親子枝には関係する結婚リンク情報が含まれている.従って,A→Bの親子関係が複数存在する場合には親子枝も複数発生する.しかし,軸線グラフのトポロジーでは多重枝は意味がないので,「親子関係の重複登録は行わない」としたのだが,「軸線ノードでMOTHERLESSでない」というエラーが出ている.つまり,軸線枠を配偶者が所有するという状態になっている.これを回避するにはどうすればよいか?

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

  1. ADG一般の最長鎖検定をサポートする@20210207 1箇所
  2. VerticallyTightenHasseDiagramを廃止する@20210209 1箇所
  3. GoodSonをシフトしたときはgoodson解除しない@20210209 2箇所
  4. CARDLINKが軸線でもjikusenでないNAMEBOXは可とする@20210209 1箇所
  5. ESTABLISHMAINEXPERIMENT@20210210 4箇所
  6. GetHasseDiagramで推移枝をすべて削除する@20210210 2箇所

TOPOLOGY::CallBuildShaftLineの中から呼び出されているBuildShaftLineを廃止し,CallBuildShaftLineでは軸線グラフの反鎖リストを直接参照して,軸線ノードを決定するように書き換えた.このとき,軸線グラフの枝に埋め込んでいた結婚枠への参照を使わずに軸線枠を決定できるようにした.これで軸線ノードと軸線枠を軸線グラフから直接決定できるようになったが,MOTHERLESSの問題に対処する必要がある.CheckMarchainで「上位人名ノード不整合」エラーが出た後もエラ-は延々と続き収拾がつかないので,setGoodSonを止めて以下を得た.

image

エラーは解消したが,軸線は通っていない.CallBuildShaftLineで軸線枠がMOTHERLESSの場合,親の拡張子ども枠に移動するようにして軸線は通るようになったが,別のエラーが発生している.下図はSUW(ShowUnderWear)で強制描画したものだ.

image

PAIRBOX::SwapBundledPairでCheckNoCommonEndPointのエラーが発生している.この問題は軸線とはまったく別個の問題と考えられるので別途シューティングする必要がある.

ZTシステム構成図7.ZELの直系血族図を#30 MDBで開いて,PAIRBOX::SwapBundledPairで停止する.この関数は共有端点束のノード対を区間の長さによってソートするものだが,予備的な検査で共有禁止ノード対が検出されたために停止している.障害ノードはPAIRBOX #1440:#1415 nodule(14)→#1174(4)だが,このノードは端点共有の代表ノード対だ.このようなノード対が複数存在する場合にも対応できるようにSwapBundledPairを整理して,ソートしてから共有禁止ノード対を見るようにした.

移動対象ノード対はPAIRBOX #1414:#1381 nodule(10)→ #1174(4)だ.この後,RepairCommonEndPointの出口のCheckPairBoxで「端点一致でsamepointゼロ」のエラーになる.⇒PAIRBOX::searchCommonPairに不備があった.「NOCOMMONPAIRノード対は端点共有できない@20210211」のように修正して解決した.

image

▲上記サンプルの全体図を#30 MDBで開いてMARGBOX:GetUpperNodeで停止した.「親ノード不在」が起きている.

短い軸線・伸びる軸線を描画する

STAGE6.ZELをNLIST LISTNODE@61でソートすると,SIMPLEGRAPH:GetLongestChainで無限ループが発生する.「反鎖リスト(同世代ノードリスト)を生成する」の段で,「重複パス上の(基準ノード以外の)任意のノードを削除する」の操作に失敗している.ループに入る直前のグラフをインポートすると下図のようにすでに最長鎖のみの状態になっている.

image

しかし,この図面はdumpgraphと内容が一致しない.ダンプでは節点数=6 枝数=8となっているのに,図面では11点も表示されている.

デスクトップ上のCSVを削除して作り直ししたら,開けなくなった.⇒CSVをインポートしたときも同じ動作になる.つまり,GetLongestChainで無限ループしている.CSVファイルをテキストエディタで開いてみると,確かにLISTは親を2つ持っている.NLISTとnlistだ.その後は,LIST→DATALIST→nodule→NODULEのように単線になっている.最下位のNODULEは自己ループを持っている.⇒初歩的な論理ミスがあった.下図のようになるのが本当だ.

image

仕上がりはこんな感じだ.

image

NLISTは「この図面」では親を持たないので先祖ノードになっているが,軸線図としては正しい.もう一つ反例がある.#201 noduleだ.このCSV(ループ直前でエクスポート)は別アプリで開けた.

image

反鎖リストの長さは11で,データ数は1, 1, 1, 1, 1, 4, 5, 4, 4, 1, 1となっている.上の図とかなり異なる並びになっているのは,世代番号によって区分しているためだ.⇒GetLongestChainにもう一つ誤りがあった.ループ中でノードが削除される場合があるのに,次のノードを取り出すのに削除されたノードを参照していた.⇒処理に入るまえに次ノードをあらかじめ取り出すようにして解決したが,まだ不具合が残っている.TREEVIEW::baseboxが空になってしまう.

この人名枠は昨日新設したSelectBaseBoxで設定しているが,初期ステージで設定したnodule(0)がExtractBox2LDRで削除され空の状態になっている.TREEVIEW::CleanSansyoの中で対象オブジェクトがbaseboxの場合にはSelectBaseBoxを実行するようした.これで問題は解決したが,BUILDCENTERLINEフェーズでCallBuildShaftLineを実行した後も,SelectBaseBoxを実行するようにした.

同上サンプルの親族種別=5(法定親族図) #1 name=couplingでソートして,TOPOLOGY::FindJikusenAncestorで停止した.軸線グラフ上に基準ノードが存在しないというエラーだ.MakeParentChildGraphで生成した直系血族グラフをエクスポートして確認しておこう.

image

節点数26,枝数32で基準ノードも健在だ.GetLongestChainの結果を見てみよう.⇒ダメだ.壊滅している.

image

GetLongestChainの中から呼び出されているGetHasseDiagramの出力を見てみよう.GetHasseDiagramでは絶対世代番号を確定し,推移枝を除去する.

image

総点数は変化していない.次の反鎖リスト(同世代ノードリスト)を生成するという段で失敗しているようだ.このブロックでは重複パスを削減してグラフを単線化することを行っている.パスの削減は一世代づつ実施され,そのたびにMakeAntiChainListで反鎖リストを更新して単線になっているかどうかをチェックしている.MakeAntiChainListではCOMPLISTを使って反鎖リストを生成しているだけで,節点や枝の操作は行っていない.最初に2段目のpagesetupが削除される.ノード削除後,グラフは下図のように変化する.

image

いや,違う.削除されたのはpagesetupではなくて,familytreeだ.確かにこれはまずいかもしれない.familytreeが削除されると,coupling→pagesetupが直結して,最長鎖が短縮してしまう.しかし,仮に短くなったとしても最終出力が得られなくてはならない.まず,こちらの方を先に調べることにする.この後は下図のようになる.

image

軸線が短縮してしまうのは仕方ない.pairlistとnlistが残っているのは末裔ノードとしてロックされているからだ.後はこの2つのノードを削除するだけなのだが,どうなるのだろう?末裔ノードはpairlistとnlistの他にnoduleがある.重複パスを削減対象となったノードは自己ループを持っていても削除される.この3つのノードには優劣はない.重複パスカットで見ているのは「基準ノードは削除されない」という一点だけだ.というのは,パスが重複している限りはどのパスを選択しても到達可能と考えられるからだ.しかし,今の場合は少し事情が違う.

pairlistとnlistはnoduleと同位ではあるが,noduleの方は「パスを持っている」だけの優位性がある.従って,「孤立点となっている場合には削除」としなくてはならないだろう.DeleteIsolatedNodeでは孤立点は削除しているが,自己ループを持っている限りは削除されない.これを修正して「自己ループしか持たない孤立ノードも削除対象」とするのが早いのではないか?⇒IsDeadEndPointという関数を新設して,「パスを失った末裔ノード」つまり,自己ループだけになったノードを検出できるようにした.これで一応動くようになって,下図が出力された.

image

一応これはこれで筋は通っているが,やはり最長軸線と呼ぶのには抵抗がある.推移枝をすべて削除してしまえばこのようなことは起こらないが,そうすると軸線が引けない場合が発生する可能性がある.どうすればよいか?現行論理では重複パスのカットでは枝ではなく節点を対象に削除しているので,その節点が接続している枝が推移枝しか持たないのかどうかということはにわかには分からない.

しかし,最長鎖には推移枝を含まないと言い切れるかどうかも疑問だ.どのノードを残すべきかの判定が必要になる.いや,そもそも,その枝以外ない場合は推移枝を残すという方式が間違っている.ここで削除したいのは,まさにそのような枝であり,それをあらかじめ排除しておけばこのような問題は発生しない.⇒GetHasseDiagramで推移枝は無条件に削除するようにした.この結果,下図の出力を得た.

image

これが正解だ.昨日のサンプルにも「伸びた軸線図」がいくつかあるが,すべて間違いだと思う.「短い軸線」が存在するのは避けられないが,「伸びる」ということは原理的にあり得ない.なぜなら,伸びた軸線があるとすれば,必ずそれを代替する伸びていない軸線があるはずであるからだ.GetHasseDiagramは2箇所で使われている.①血統軸線図,②重婚クラスタ検定だが,少なくとも血統軸線図の場合はこの方式で間違いない.Stage6.zelの完全木テストが通った.

image

実際の出力を見ると,かなり「伸びた軸線」が発生している.これは避けられないものなのかどうか?チェックする必要がある.下図はStage6.zelをTreeViewでソートしたものだが,軸線は伸びている.

image   image

一見すると伸びない軸線が存在するように見えるが,それらはTreeViewを通っていない.血統軸線図の検定を行うときの対象グラフはそもそも直系血族図なので,これらのノードはグラフ上に現れない.実際,TreeViewの直系血族図(上の右図)に含まれるのは,軸線上のノード8点とpagesetupの1点だけだ.

▲ZTシステム構成図7.ZELをMDB @30で開いて,MARGBOX:CheckMarchainで「上位人名ノード不整合」のエラーが出た.

「 非多重グラフで多重枝の登録は不可 」というエラーが出ているので,TOPOLOGY::MakeParentChildGraphで「親子関係の重複登録は行わない@20210210」としたが,果たして問題ないだろうか?一人の人物が,同じ子どもを対象とする複数の養子関係を結ぶということはあり得るので,A→Bという親子関係は異なる結婚に関係して複数あり得ると考えるべきなのではないか?しかし,現行論理では多分多重カードを許したとしても,そこまでは対応していないものと思われる.あるいは,このサンプルではそれが必要になるのだろうか?「軸線ノードでMOTHERLESSでない」というエラーも起きている.

MOTHERLESSというのは「配偶者を持たないか隠蔽された本人直下の単親婚子ども枠」となっているが,本人直下の子ども枠は有配偶者の場合でもMOTHERLESSとされるのではなかったろうか?実際,このエラーメッセージはそのことを言っているものと思われる.多重枝を持つのはよいが,それをハンドリングするのが難しい.いよいよ奥の手を使うときが来たのではないだろうか?

重婚クラスタ図と既存ハッセ図を比較する

CSVファイルをインポートすると,どこかでこのファイルに書き込みを実行している.通常系図ファイルはDLL側でオープンされるが,CSVファイルの場合は,VB側でオープンされ,VBで読み取ったレコードがDLL側に送られるようになっている.CSVファイルを読み取り専用に設定して開いた場合には書き込みは行われない.ただし,エラーも発生しない.⇒テストはリリース版で実行しているが,リリース版のコードにテスト用のコードが残っていて,書き込みを実施しているのではないか?リリース版では少なくもテスト用のエクスポートは止めておかなくてはならない.これではテストにならないので,もう一度リリース版を作り直してみよう.⇒Version 2.2.0.025 Release 2021-02-09を所内解放した.

TribeRelocationのステージ【4】重婚同類グラフ検定でBuildSameGeneMarriageGraphを実行して生成された「重婚クラスタグラフ」をインポートして表示すると下図を得る.

image

節点数27,枝数34の非循環的有向グラフだ.これからGetHasseDiagramでハッセ図を生成して得られる図面が重婚クラスタ図だが,どうもうまくいっていない.節点数は変化していないが,すべての枝を失ってしまっている.

image

どこで失敗しているのだろう?かなりおかしい.多重クラスタグラフがいつの間にか軸線図グラフにすり替わっている.いや,勘違いだ.GetHasseDiagramは血統軸線図検定の中でも呼び出されている.⇒SIMPLEGRAPH::ExportGraphが動作していない.TOPOLOGY:SaveClusterGraphなら保存できる.

ExportGraphは不要なフィールドを省いてコラム数を11まで削減したものだが,少なくとも軸線図ではこれで動いている.⇒SIMPLEGRAPH:ExportGraphの共通ブロックにCARDLINKを使っているところがあった.場合分けしなくはならない.⇒多重クラスタグラフが取れた.多重クラスタ図も作れる.下図左はGetHasseDiagramで出力した重婚クラスタ図,右は既存ハッセ図検定の出力.

image   

節点数は27で同じ,枝数は30に減少している.世代数は10で既存ハッセ図検定の出力と同じだ.比較してみよう.2つの図面の基本的な違いは,推移枝をカットする位置と思われる.重婚クラスタ図では下流でカットし,既存ハッセ図では上流でカットするようになっている.しかし,この違いは必ずしクラスタ検定と既存ハッセ図検定の差という訳ではない.GetHasseDiagram(新設した関数)で引数のseed(起点)を変えてやると下図のような図式が出力される.

image

ただし,GetHasseDiagramの中で実施している上流検定と下流検定の順序も入れ替えている.つまり,出力結果の図式は手順によって変わるということが言える.現行では推移枝を無差別に削除しているが,その枝が端点ノードの唯一の枝の場合は削除しないように変えてみよう.

image

大分それらしくなってきたので,この方向でもう少し作業を進めることにしよう.TestInevitableMultiZeroとVerticallyTightenHasseDiagramを廃止して,GetHasseDiagramに置き換えた.出力を見てみよう.左が現在,右が従来方式だ.

image   image

3段目のundosysは本来4人兄弟なのだが,従来方式ではそのうちの2人が抜けて下の方に移動してしまっている.これは,少なくともこの図面の制作意図には反しているように思われる.このサンプルの原図であるZTモジュール構成図は,モジュール構造を可視化することを目的とするものであるからだ.どちらの図面も軸線が複線になってしまっている.世代関係を維持しながら,この軸線を通すためには,どうしてもnoduleを3世代分垂直シフトする必要がある.垂直シフトを許すとフロート婚が発生する可能性があるために現行では禁止されているのだが,それを解除するとどうなるかを見てみよう.⇒まだエラーは出ているが通った.

image

これでよいのではないかと思う.すべてのノードが重婚クラスタ検定で割り当てた絶対世代番号の位置に配置されている.これができれば,問題はほぼ解決したも同然だ.どんな大風が吹いてもびくともしない構造物が作れそうだ.

STAGE6.ZELを#6 undosysで開いて,MARGBOX::setgoodsonのエラーが出る.(PHASE > CLEARTABLE && GoodSon && (!goodson || !goodson->GoodBox) && !IsExtractDummy() && !OnSetGoodSon && !OnRestoreExtractBox && !OnBuildShaftLine && IsVital() && !OnsetGoodSibling && !OnCancelBetweenTwo) という理由だ.つまり,「GoodSonを訳もなく空にしてはならない」という意味だ.

コメントには「対象ノードがgoodsonである場合はgoodson解除する@2018-03-30」とある.これはカードシフトされたカードはgoodsonにはなれないという意味だが,今の場合軸線なのでgoodsonを解除することはできない.⇒「GoodSonをシフトしたときはgoodson解除しない@20210209」としておこう.今度は,makeGoodSonで停止した.

(!head->IsaSibling() && head->GoodSon->getmyhome() != head)という理由だ.「単体枠でGoodSonを持っているがGoodSonのボックスはこの結婚枠でない」というコメントが付いている.この結婚枠が設定しているGoodSonは#266nodule(0)で抽出されたノードだ.結婚枠に入っているのはnodule(4)でnodule(4)→nodule(0)のように接続されている.noduleの仮ノードは(1)が欠番で,(0)から(12)までの12個表示されている.undosysとnodule(0)をつなぐ抽出枠チェーンは(4)→(5)→(6)→(0)のように繋がっている.(4)→(5)→(6)はダミーなので(0)がGoodSonとなるのは妥当だろう.

軸線がシフトすることを認めるとしたのだから,ここでは停止しないというのでよいのではないだろうか?ないし,条件に「EXTRACTBOXでない」を付け加えてもよい.⇒今度はNAMEBOX:MakeTooYoungWifeで停止した.「カードシフトによる抽出ダミー枠のチェーンをTYWの長い尻尾に転換」しようとしている.これはまずいのではないか?

NAMEBOX::CardShiftDirectではGoodBoxをTYWに転換することは禁止している.従って,このノードは軸線ではない.MakeTooYoungWifeではNAMEBOXではなく,CARDLINKがjikusenであるかどうかをチェックしているが,NAMEBOXが軸線でなければ許可してよいと思う.これでエラーはすべて解消した.ダミーノードにはjikusenのマークは付与されないので,可視ノード数とjikusenの個数が一致しない場合がある.couplingでソートすると下図のようになる.

image

ここまでやるか!?という感じもあるが,まぁ,やるっきゃないね…

STAGE6.ZELの#30 MDBで「軸線が通っていない」エラーが出た.出力では一応通っているように見えるのだが…

image

水平座標の比較の基準としてTREEVIEW::baseboxを使っているのだが,これ以外のすべての軸線ノードとの間で不一致が生じている.それも-56 <> 108という大きな差が出ている.軸線上のノードはMDB(2)だ.baseboxはMDB(0)で不可視の消去された仮ノードだ.baseboxの選定が悪いということになる.baseboxはSETRELATIVEフェーズで設定されている(その前にも仮設定されていはいるが…).

条件は始系列に属する可視ノードだ.⇒軸線が決定したときに見直すか?ないし,baseboxが不可視になったときに取り直しをするかのどちらかが必要だ.⇒TREEVIEW::SelectBaseBoxという関数を新設し,baseboxが不可視になったときにはこの関数を実行するようにした.

▲上記サンプルを#43 graph1でソートして,SIMPLEGRAPH:GetLongestChainで無限ループしている.重複パスの単線化に失敗している.完全木テストで連続実行している場合にのみ起こる.#43 graph1で起動した場合には発生しない.いや,#43ではなく次の#61で起きているようだ.

STAGE6.ZELの全体図最長鎖グラフが出た

STAGE6.ZELの全体図最長鎖グラフが出た.この図はSTAGE6.ZELのすべてのカードを節点とするグラフで,親子枝のうち推移枝だけを除いたものだ.通常最長鎖グラフというときには最上段の先祖ノードと最下段の末裔ノード以外の行き止まりノードは削除されているので,これは,最長鎖グラフと呼ぶより,むしろハッセ図と見た方がよい.

image

このグラフから具体的な最長鎖を切り出すのは難しくない.基準ノードはこの軸線(最長鎖)には含まれていない.このグラフを取り出すために既存のGetLongestChainを少し加工しているが,現状のGetLongestChainはBuildHasseDiagramとリネームし,GetLongestChainで処理を止めている部分とFindJikusenAncestorの中でMakeAntiChainListを合体して,GetLongestChainとすることにしよう.つまり,BuildHasseDiagramでまずハッセ図を生成し,GetLongestChainではそれから最長鎖を切り出すという手順になる.

さて,これからが本番だ.まず,既存のハッセ図検定で作っているグラフをエクスポートするところから始めよう.ハッセ図の節点は重婚クラスタで枝は親子関係だ.その前に試しておくことがある.SIMPLEGRAPH::ExportGraphではレコードのすべてのフィールドを送っているが,多分必要な部分だけ送ることができるはずだ.まず,そのことを確認してみよう.⇒問題なさそうだ.「番号,性別,氏名,よみ,所属,肩書き,父母数,父番号,母番号,続き柄,配偶者」だけを送るようにした.SIMPLEGRAPH::nodtypeにノードのクラス種別を設定するようになっているのだが,使われていないようだ.ハッセ図を出してみた.上の全体図最長鎖グラフと比べるとかなり違う.

image

最長鎖図の世代数は11に対し,ハッセ図は10世代.最長鎖図には軸線上のものを除き,葉が7枚あるが,うち1枚以外はすべ下向きだ.一方ハッセ図では上向きが5枚,下向きが2枚でまったく違うものになっている.なぜか?理由は簡単だ.この2つはそもそも対象グラフが異なる.前者の節点は系図上のカード,後者は重婚クラスタだ.

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

軸線図グラフの動作はほぼ仕上がっているように思われるが,軸線が通らないサンプルが散見される.たとえば,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だ.もう一度洗い直しするしかないだろう.