どうも失敗してしまったようだ.ClearTableには2種類ある.一つはBASETABLE用で,もう一つがいま問題のTOPOLOGY用のものだが,取り違えていた.間違え易いのでリネームしておくことにする.BASETABLEの方をResetTable変更した.
NODULE クラス共通定義の整理
計算効率は作業効率に大きく影響するので,受忍限度内にファイルが開けるようになるまで処理速度を向上させるという目標でパフォーマンスの改善に取り組んでいるところだが,目下のターゲットとなっているClearTableでは,処理速度を上げることよりも,処理の透明性を確立することを主目的とする方向に転じることにする.処理の透明性は最重要課題であり,それが崩壊してカオス状態に陥る場所がCLEARTABLEフェーズであると考えられるので,この場所を完全に抑え込むことができれば,完全にクリーンなシステムという最終目標はほぼ達成されたと見てもよいはずだ.これはNODULEシステムの純度を上げ,完全に閉じたシステムとして独り立ちさせるためには不可欠の工程と考えられる.
このためには,COMMONHEADERCOMMON/SHORT/longbrPARTをきっちり整理する必要がある.手始めに共通ヘッダ定義の部分をテーブルの形にまとめてみた.NODULEクラスの仮想関数の一覧もテーブル形式にまとめた.これで大分見通しがよくなったのではないかと思う.Open Live Writer ではテーブルの編集には十分ではないので,ログインしてWordPressのエディタを使って編集するようにした.テーブルサイズがかなり大きいので持て余し気味だが,まぁ,なんとかなりそうだ.
CLEARTABLEは基本的には描画オブジェクトを初期化するというフェーズであり,基本データであるリンクデータには手を触れないというのが原則であると考えられるが,現行では系統並び替えに入る前に一度EraseTreeViewで描画オブジェクトを一度完全にパージしているはずなので,あるいは,すでにまるごと不用になっている可能性もあるのではないか?それとは逆に,画面要素を可能な限りキープして,更新された部分だけを再計算するという考え方もある.そのようなことが可能であるのか,可能でないのかも検討課題とする必要があるのではないか?
ClearTableを完全に停止してみたが,まったく問題なく動作している.ただし,前回は系統並び替えで14秒だったのが17秒と逆に増加している.これはどういうことだろう?いや,今回は復活させても17.75秒なのでやはりいくらかは早くなっている.時間が遅くなったのは,別の理由と思われる.この逆にEraseTreeViewを実行しないとどういうことになるのかを見てみよう.⇒まったく問題なく開けた.系統並び替えの時間は若干短縮して13.17秒になった.
▲ファイルを初期オープンしようとするとき,EraseFamilyTreeが重複して掛かってくる.⇒アプリ側はファイルをオープンする前にデータベースのクローズ要求を出してくる.DLL側ではファイルオープン処理の中でデータベースクローズを実行しているため.特に実害はないので,ママとしておこう.
ClearTableとEraseTreeViewの両方を止めてもまったく問題なく動作している.つまり,これらの処理はすでに不用になったと考えられる.これで,不連続点ないし不連続面が完全に払拭されたということになる.もはや最終形態に近いと言ってもよいのではないだろうか?これらの処理が入っている理由の一つとして,デバッグ時の再現性の問題がある.つまり,初期状態が完全に一致していないと,再現が困難ないし不能になる場合があり得るため,厳密に同じ状態から処理を開始する必要があるという理由だ.しかし,仮にそのような問題があり得るとしても,それはそれでまた別の方法で対処可能と考えられるので,ここでは,強引に壁を突破して前に進むことにしよう.
再現性の問題を除けば,(動作不良が発生しない限り)完全に連続した環境をキープすることに関しては何の問題もない.むしろ,そうあるべきだと言えるだろう.最後に残る問題は,系統並び替えと部分的な再描画が両立するものかどうか?という点に絞られてくるのではないか?トポロジーが変化しない限りはYリストも不変であり,座標値の再計算だけを行えばよいはずだが,トポロジーが変化したときには当然Yリストにも反映されなくてはならない.それを局所的に対処することが可能であるのなら,部分的な再描画も可能になる可能性が出てくる.
再描画の問題は局所計算の問題にも関わりがある.つまり,巨大図面の一部のみを計算して,必要になったときのみ,必要になった部分のみを再計算するという考え方だ.つまり,グーグルマップのようなことができるようになるためには,どうすればよいのか?という話なのだが…
NODULE クラス共通定義
NODULE クラス共通定義
COMMONHEADERBASIC(BASECLASS, CLASSNAME, CID, VSLOTS) | ||||||
No. | scope | return value | function name | parameters | process | macro |
1 | virtual | char* | classname | void | #CLASSNAMEの取得 | 基本 |
2 | static | unsigned short | classid | void | CIDの取得 | 基本 |
3 | int | getclasssize | void | sizeof(CLASSNAME)の取得 | 基本 | |
4 | unsigned short | getcid | void | cidの取得 | 基本 | |
5 | nodule ** | VSLOT | void | noduleポインタのアドレス→(BPTR)this + sizeof(BASECLASS)) | 基本 | |
6 | nodule *& | _SLOT | int edan | int bases=BASECLASS::_vslotsize(); if (edan <= bases) return BASECLASS:_SLOT(edan); else { nodule **vslot=CLASSNAME::VSLOT(); if (vslot) return vslot[edan – bases – 1]; else return null; } |
基本 | |
7 | virtual | nodule *& | SLOT | int edan | if (edan == EXTRASLOT) return extraslot(); else if (edan == REFERIST) return referlist(); int xtotal=vslotsize(); if (edan > xtotal) { if (IsNotVital()) return NODULE::SLOT(edan); else return null; } return CLASSNAME:_SLOT(edan); |
基本 |
8 | int | _slotsize | void | VSLOTS | 基本 | |
9 | int | _vslotsize | void | CLASSNAME::_slotsize() + BASECLASS::_vslotsize() | 基本 | |
10 | int | vslotsize | void | CLASSNAME::_vslotsize() | 基本 | |
11 | void * | dataptr | void | (BPTR)this + sizeof(BASECLASS) + VSLOTS * sizeof(long) | 基本 | |
12 | static | long | datalen | void | sizeof(CLASSNAME) – sizeof(BASECLASS) – VSLOTS * sizeof(long) | 基本 |
13 | void | dataclean | void | memset(dataptr(), 0, datalen()) | 基本 | |
14 | void * | dataPtr | void | dataptr() | 基本 | |
15 | static | long | dataLen | void | datalen() | 基本 |
16 | void | dataClean | void | memset(dataPtr(), 0, dataLen()) | 基本 | |
17 | void | CleanSlot | bool refclear=false | int start=BASECLASS::_vslotsize() + 1; int slotsu=CLASSNAME::_slotsize(); for (int i=start; slotsu–; i++) clearSlot(i, refclear); |
基本 | |
18 | void | Clean | void | doClean(); int slotsu=CLASSNAME::vslotsize() + 1; for (int i=0; slotsu–; i++) { nodule *snod=SLOT(i); if (snod && snod->getpnode() == this && snod->getpnum() == i && snod- >IsVital() && !snod->IsEmbedded()) snod->Clean(); } |
基本 | |
19 | void | setpid | void | vslots()=CLASSNAME::_vslotsize(); if (!pclassid[CID]) { pclassid[CID]=(unsigned short) BASECLASS::classid(); vsfunctable[CID] = (VSLOTFUNC)&CLASSNAME::_SLOT; } |
基本 | |
COMMONHEADERPART(BASECLASS, CLASSNAME, CID, VSLOTS) | ||||||
20 | int | CleanSansyo | nodule *object | 固有 | ||
21 | virtual | void | Dispose | void | 固有 | |
22 | virtual | char * | dump | char *buff | 固有 |
上記2つの共通マクロの他にCOMMONHEADERSHORTが定義されている.COMMONHEADERSHORTにはCOMMONHEADERBASICが含まれる.COMMONHEADERPARTは,COMMONHEADERSHORTに上記3関数の宣言を追加したものである.関数本体は別途記述されなくてはならない.
#define COMMONHEADERSHORT(BASECLASS, CLASSNAME, CID, VSLOTS) \ COMMONHEADERBASIC(BASECLASS, CLASSNAME, CID, VSLOTS) \ void *operator new (size_t size, nodule *moto=NULL, int eda=0, long ds=0, int addslot=0, short cid=CID, char *namstr=#CLASSNAME){ \ return nodule::operator new (size, moto, eda, ds, addslot, cid, namstr); } \ void operator delete(void *nod, nodule *moto, int eda, long ds, int addslot, short cid, char *namstr){ \ nodule::operator delete(nod, moto, eda, ds, addslot, cid, namstr); } \ void operator delete(void *nod){ nodule::operator delete(nod); } |
#define COMMONHEADERSHORT(BASECLASS, CLASSNAME, CID, VSLOTS) \
COMMONHEADERBASIC(BASECLASS, CLASSNAME, CID, VSLOTS) \
void *operator new (size_t size, nodule *moto=NULL, int eda=0, long ds=0, int addslot=0, short cid=CID, char *namstr=#CLASSNAME){ \
return nodule::operator new (size, moto, eda, ds, addslot, cid, namstr); } \
void operator delete(void *nod, nodule *moto, int eda, long ds, int addslot, short cid, char *namstr){ \
nodule::operator delete(nod, moto, eda, ds, addslot, cid, namstr); } \
void operator delete(void *nod){ nodule::operator delete(nod); }
- COMMONHEADERBASIC 1(nodule)
- COMMONHEADERSHORT 7(ARRAY, BASETABLE, COUPLING, NODEREFLIST, nlist, NLIST, KAKEIZU, PSCLASS)
- COMMONHEADERPART 38(CARDLINK, MARGLINK, CARDTABLE, MARGTABLE, longtable, Bobject, DATALIST, LIST, REFLINK, PARTIALNAME, LINKTABLE, TOPOLOGY, FAMILYTREE, GENEBOX, GENELIST, PAIRBOX, …)
仮想関数 | ||||||
No. | scope | return value | function name | parameters | process | N/n B/P |
1 | virtual | nodule *& | SLOT | int edan | NB | |
2 | virtual | char * | classname | void | “NODULE” | NB |
3 | virtual | long | getsize | void | size | N |
4 | virtual | char * | dump | char *buff | NP | |
5 | virtual | char * | Dump | void | N | |
6 | pure virtual | void | Dispose | void | NP | |
7 | virtual | int | getclasssize | void | sizeof(NODULE) | NB |
8 | virtual | int | getSlots | void | vslotsize() | N |
9 | virtual | int | CleanSansyo | nodule *object | refcnt | NP |
10 | virtual | int | vslotsize | void | NB | |
11 | virtual | void | void | N | ||
12 | virtual | void | clean | void | N | |
13 | virtual | void | Clean | void | clean() | NB#18 |
14 | virtual | void * | dataPtr | void | (BPTR)this + sizeof(NODULE) | NB |
15 | virtual | long | dataLen | void | NB | |
16 | virtual | void | dataClean | void | memset(dataPtr(), 0, dataLen()) | NB |
17 | virtual | int | Sansyo | int slots, nodule *object=NULL | n |
QUICKDBを書き直して劇的に高速化
QUICKDBを大幅に書き直してKAKEIZU::getmarriageとgetcarddataでレコード全体を取り出すように修正したところ,データベースのロード処理が劇的に高速化した.これまではかなりの時間待たなくてはならなかったところ,ほとんど瞬時に完了するようになった!1万点を超えるサンプルをそこそこの時間で開けるようにしたいのだが,まだまだ手が届かない.ボトルネックになっているのはセグメント検定だが,それ以外では,ClearTableにかなりの時間を浪費している.
いや,その前にステージ7リンクの検証,ルックアップテーブルの生成にかなりの時間が掛かっている.ここでやっているのは,①CheckDataLink,②MakingLookUp,③NameSortだけだ.⇒どうも重いのは,③NameSortのようだ.改善の余地はあるだろうか?
53直系血族サンプルで,CheckDataLinkの所要時間は1.6秒,MakingLookUpは0秒,NameSortは344msだ.CheckDataLinkはZELファイルを読み込んだ場合ではパスしてもよい.Collatz4000X.ZELでは,NameSortで14.3秒掛かっている.⇒NameSortはlookup.tableを直接操作しているが,ループの中で前詰めを実行しているので,余分な手間が掛かっているのではないだろうか?書き直してみよう.
どうも,大した効果は得られなかった.13.953秒なので300msくらいしか短縮されなかった.いや,そもそも,どちらのサンプルもlookupには穴が空いていないので,前詰めによる時間浪費はミニマムなものでしかない.とりあえず,時間短縮するならNameSortをやらないようにするしかないだろう.これは仕様の問題であり,そう決めればよいというだけの話だ.⇒対処した.
ClearTableは後から見ることにして,三極検定,中でもセグメント検定の効率化が可能かどうかを見てみよう.⇒checkをオンにしただけなのだが,どこか壊してしまったのだろうか?動作がおかしくなってしまった.セグメント検定用のツールが壊れている.それにしても,checkを入れただけで副作用が発生するというのは考えづらい.⇒関数の実行に掛かる時間を測定・表示するために挿入したコードが誤動作していたようだ.GetTickCountで取り出した値はULONGLONGで,printfの書式の説明では%dでlong longを表示できるように書かれていたのだが… 誤動作を避けるため,引数でulong(xxx)で型変換するようにして収まった.
セグメント検定の実行ルーチンであるHorizontalSegmentは小さい方のサンプルでも600msくらい掛かっているが,その中から呼び出しているCheckMaximalSegmentが一回の呼び出しで200ms掛かり,それを3回実行しているので,HorizontalSegmentの実行時間のほとんどはCheckMaximalSegmentが費やしていることになる.従って,CheckMaximalSegmentの処理時間を短縮できれば,それだけHorizontalSegmentも軽くなるということになる.
しかし,その前に3回も実行する必要があるのかどうかもチェックしておく必要があるだろう.⇒3回を入口検査の1回だけに削減した.これだけでもかなり高速化した.トイレに入っている間に大きい方のサンプルが描画できるところまで来た.ここまで来ればそこそこ実用になる.大きいサンプルではHorizontalSegmentを1回実行するのに2.6秒くらい掛かっている.このうち,CheckMaximalSegmentが2.3~2.4秒で大半を占めている.CheckMaximalSegmentはもう少しダイエット可能だと思われるが,その前に系統並び替えに入ってから三極検定に移るまでに掛かっている時間コストを調べておこう.
系統並び替え全体では,サンプルA(1931点)で14秒,サンプルB(12243点)で316秒≒5分だ.まず,バックアップを取ってから始めよう.TOPOLOGY::ClearTableではlookupを使っていない.これはかなり不利だと思う.書き直してみよう.⇒57秒掛かった.修正前の時間を測っていないので,改善しているのかどうかは分からないが,ClearTableだけで1分というのはやはりかかり過ぎのような気がする.なぜだろう?結婚ではlookupを使わないことにしたのだが,かなり疎なテーブルになっている.結婚リンク数が1393のところ,テーブルは3578で3倍近くある.
確かに飛び番になっている.復活させるのはそれほど手は掛からないと思うが…サンプルBでは逆のことが起こっている.PDB,MDBとlookupのカウントが完全に同じだ.つまり,テーブルは最初から密に詰まっている.おそらく,これは以前保存するとき,基準ソートされた状態で保存していたのだろう.基準ソートするとテーブルは完全に前詰めされた状態になる.現在もそういう仕様になっているかどうかは分からない…いずれにせよ,サンプルBに関してはlookupを使っても効果はまったく出ないということになる.LINKTABLE:CheckIfMargValidという検査ルーチンはNOCHECKDATALINKで止めておこう.
もう少し,修正のフィックスを進めよう
もう少し,修正のフィックスを進めよう.
- UpdateDiagramProcを使う@20230212 → 確定
- getnewRefnumを使わない@20230201 → 確定
- emptyrecnを使わない@20230201 → 確定
- Renumberではリンクを移動しない@20230116 → 確定
- TOPOLOGY:GetRecordを使う@20230115 → 確定
- lookup未対応@20230110 → 確定
- MARGTABLEのlookupを廃止する@20230114 → 確定
- RenumberではUNDOをリセット@20230116 → 廃止
- checkMargPointの中でHeapBranchを実行している@20220415 → 確定
- REDLINEオーバーでループから脱出@20220420 → 確定
大体片が付いた.UpdateDiagramはかなり難があるので,原則として今後は使わないということにする.やはり,計算精度の問題があり,画面が崩れてしまう.系統並び替えを実施すればノーマルに表示できるので,ここでは時間効率より見た目を優先することにする.
▲データベースのロードに時間がかかり過ぎる.ファイルはバッファに読み込まれているが,単純なテキストの状態で放置されているため,毎回のシークに多大な時間を浪費しているように思われる.少なくとも,レコードを管理するテーブルのようなものが必要だ.⇒QUICKDB::LoadQuickを実装した.動作している.
InitLinkTableでは,KAKEIZU::getmarriageとgetcarddataでレコードを取り出している.この関数を直接書き直してやればよい.これらの関数では,各項目ごとにQUICKDBに要求を出してデータを受け取っているが,QUICKDB側でまとめて送り出してくるように書き換える必要がある.PICKコマンドを送信すると,DB側ではitemdata[]という配列に項目データを格納し,取り出しの準備を行うようになっている.
原因は計算の中で行っている単精度実数演算
どこでオーバーフローが起きているのか?オーバーフローを起こしている関数を突き止めたいのだが… TREEVIEW::setMappingをチェックしてみよう.初期フェーズでFramePositionからの呼び出しがある他はどこからも呼ばれていない.⇒不良は主に結婚点の不一致が原因と思われる.ここではGetBrotherJointで結婚点座標を計算しているが,MARGBOX:TopCenterからDEV2というマクロが実行されている.このマクロは内部でfloat演算を実施しているが,単精度実数の精度は32ビットということになっているようだ.
!解けてしまった!原因は計算の中で実施している単精度実数演算にあることが確定した.そもそも,2ないし4で割る計算でシフト演算を使っていないというところが腑に落ちない.多分,単純シフトでは負数の場合は具合が悪いということでフロート演算を使うことにしたのではないかという気がするのだが… 最上位ビットを維持しながらシフトするというのはそれほど難しくないはずだ.マクロにしたのは,計算時間を節約するためと思われるが,浅慮だったと思う.実数演算にしたもう一つの理由として考えられるのは,四捨五入して丸めているという点だ.シフトでは基本的に切り捨てになってしまうから,誤差の累積を考えると必ずしも悪い判断とは言えないが…
ともかく,この計算はシフトを用いた除算に書き換えておこう.⇒そもそも,C++のシフト演算では対象が符号付き整数の場合は符号を維持した値を返している.ただし,これは右シフトの場合だけで,左シフトの場合には,結果は不定だ.これで完全に解決した.どこか別のところでも単精度実数を使っている可能性があるので,チェックしておこう.⇒「float」という語句は180箇所出現するが,すべて一括変換してdoubleに変えておこう.⇒その前に一度バックアップ.⇒216個置換された.
floatからdoubleへの切り替えで少し遅くなるかもしれない.VBではfloatを使っているところはないのだろうか?ここでは計算は実行していないので,たとえ使われていたとしても実害はないはずだが…
▲53直系血族図.ZELを開こうとして,「COUPLING::OpenFamilyBase ファイル形式が合っていません」というエラーになった.ブロック名と呼ばれる埋め込みコードが一致しないというエラーだ. ⇒ズーム倍率を単精度でファイルに保存している.⇒ズーム関係のパラメータはfloatに戻しておこう.⇒一通り修正を入れ終わったが,まだ解消していない.⇒ページ設定の読み込み不良が起きている.「これは縦書きフォントではない」というエラーも出ている.SerializeHeader はパスしている.
PNTPERLOGとPIXPERLOGをfloatに戻さなくてはならない.⇒まだ,解消しない.⇒_PAGESETUPのmultiplier(印刷倍率)はdoubleだ.しかし,この値に大雨するMAPPING::zoom_outputはフロートだ.取り敢えず実害はないと思われるので,ママとしておこう.⇒できた.大分いい感じになってきた.修正を可能な範囲でフィックスしておこう.
- フォントサイズを縮小する@20230209 → 廃止
- 単精度実数を使わない@20230212 → 確定
- 系列再配置前ではDCを操作しない@20230212 → 確定
- 結婚枠だけをセグメント検定の対象とする@20230125 → 廃止
- セグメント検定を二段階で実施する@20230129 → 確定
- CARDLINK:dispselectboxを廃止する@20230211 → 確定
- CARDLINK:dispprimeboxを廃止する@20230211 → 確定
- CARDLINK:DrawCardImageを廃止する@20230211 → 確定
- TOPOLOGY:DrawTooYoungWifeを移動@20230211 → 確定
論理座標系ではフォントサイズをつねに1で計算するというのは面白い仕様だが,最終仕様にはならない.計算負荷はそれほど大きな差にはならないと考えられるからだ.ただし,系列再配置中はCOUPLINGの論理フォントにアクセスしないという制限が守られているかどうかをチェックするのは意味があるので,やってみよう.⇒TREEVIEW::setDispParmではノード対リストの準備も行っているので,NamePropertyなどは渡す必要がある.⇒問題なく動作している.ただし,メインループの出口でTREEVIEW->setDispParm(true)を実行するようにした.
▲フォントサイズを変えると画面が崩れてしまう.「フォントサイズ1を試す@20230212」というオプションをオフにすれば,ノーマルな図式が表示される.ただし,Collatz4000X.ZELのような大きなサンプルでフォントサイズを変えた場合には,かなりひどい画面になる.系統並び替えを実行すれば整った図面に戻すことは可能だが… フォントサイズ1を試す@20230212」はオフにしてあるので,この不良はUpdateDiagramの不備によると言える.
ブログのデフォルトフォントをメイリオに変更したい
ブログの投稿専用だったVAIOでメールも受信することにした.DIGI+の 2 in 1 ノートは最初から不調だったが,メモリも拡張できず(毎朝不用ファイルを削除しないと動作しないという状態),ほっておくと自然にダウンしてしまうので,そろそろ寿命と考えるしかないという結論に達し,早期リタイアということになった.一つのSDメモリカードでメールとブログのバックアップが取れるようになったので,いざというときにもそれ一つを持ち出せばよいので分かり易い.
スマホのテザリングをUSBないしBlueteethで接続すれば,WiFiをLAN代わりに(並列で)使えるかと思ったが,うまくゆかなかった.Mouth Without Bordersでマウスとキーボードを共有しているので,無線LANがないと作業にならない… 結局,これまで通り,スマホにWiFiで接続するというトポロジーになった.作業性はこれが一番よいのだが,できれば開発機をネットから完全に遮断したかった…
ブログのデフォルトフォントをメイリオに変更したいのだが,うまくゆかない.ブログテーマのCSSを
font-family:“メイリオ”, Meiryo, ”ヒラギノ角ゴ Pro W3″, “Hiragino Kaku Gothic Pro”, Osaka, “MS Pゴシック”, “MS PGothic”, sans-serif; line-height: 1.15;
のように書き換えてみたが,効かなかった.元々はsans-serifがデフォルトになっているのだが,実際にはCaribriが使われている.これはOpen Live Writerのデフォルトなのではないだろうか?Open Live Writer のプラグインで Dynamic Template というのがあったのでインストールしてみた.Plugins→ Dynamic Template→ テンプレートを選択→ continue→ 挿入 という手順が少し煩わしいが,テキストを全選択してフォントを切り替えるよりは早いのではないだろうか?
さて,不良の原因が描画領域のオーバーフローにあるということはわかったが,どう対処すればよいか?あまり大きくないサンプルはこれまで通りの方式で描画できるようにしたいので,「動的」に対応する必要があることは間違いない.それを,どこで,どのようにやればよいか?が問題だ.一つ気になるのは,系列再配置のフェーズでデバイスコンテキストにアクセスしている可能性があるのではないか?という点だ.
整数演算は現在 int は64ビットで動作していると思われるので,32ビットを超えただけで障害が起きるというのはおかしい.系列再配置フェーズではあくまで論理座標系で計算しているはずなので,計算時にオーバーフローが起きているとしたら問題だ.まず,CDCにアクセスしている関数を扱うヘッダファイルに制限を掛けておこう.現行では,
- basetable.h DrawCardImage,dispselectbox,dispprimeboxを廃止
- Bobject.h
- BugReport.h
- comedebug.h dcMemCardImage,DefaultCardImage→ 移動
- Coupling.h
- DBSORT.h DrawTooYoungWife→ TREEVIEWに移動
- DrawingObject.h
- GeneList.h
- Lineage.h
- PageSetup.h
- SimpleTree.h
- TitleBox.h
- ZelkovaExports.h
- ZelkovaCtrl.h
の14本がある.⇒どうも,どこか壊してしまったようだ.もう一度作り直すしかない.⇒ようやくビルドできた.バックアップを取っておこう.⇒なぜだろう?フォントサイズの調整は止めてあるはずなのに,Collatz4000X.ZELが通ってしまった.26回ループして抜けている.⇒修正を戻してみよう.⇒いや,描画に成功した時点で保存しているためではないか?つまり,操作しなくてもすでに小さくなっているのではないか?どうもそういうことのようだ.
12243点,結婚数10170のCollatz4000X.ZELが開けた
ようやく難題が一つ片付いた.というか,まだ完全には終わっていないが,片付くという目処は付いた.確定的な原因は不明だが,サンプルサイズが大きくなると,座標計算のどこかでオーバーフローによる障害が発生しているように思われる.とりあえず,フォントサイズを小さくすることで描画できるところまでは漕ぎ着けた.箱は正しく描画できているのだが,名前のサイズと位置が悪い.
現行ではWin 32でビルドしているので,8バイトを超える整数ではオーバーフローが起きる可能性がある.描画フェーズ以前ではすべての計算は論理座標系で行っているはずだが,現行では論理座標系=物理座標系=ピクセル座標系になっているのではないかと思う.論理座標系は物理座標系から切り離すことができるはずであり,実際,現在はPNTPERLOGという変換パラメータを4倍にすることで,1/4に縮小している.本来なら,これで描画できてもよいはずなのだが… ⇒座標計算では縮小しているが,描画ではフォントサイズをそのまま適用しているためだろう.⇒COUPLING::m_logfont1/2/3を直接参照している.やはり,これを直接書き換えるしかないのではないか?
DrawTextLineで強制的にフォントサイズを縮小して,氏名だけは枠内に表示できるようになったが,まだ位置が悪い.また,付帯情報として表示している数字は大きいままだ.むしろ,ファイルを読み込んだときに調整した方が早いのではないか?⇒とりあえず,これが一番簡便な方法だ.暫定的にこれで運用することにしよう.
とりあえず全体を描画できるようになったが,ズーム倍率は211%止まりでそれ以上には拡大できない.また,メモリ描画環境も落ちて,生描画になってしまう.⇒SIZEOVERAUTOZOOMOUTというオプションを外したら,500%でも描画できるようになった.
これなら,まぁまぁ使い物になるのではないだろうか?一部にカードのダブりが出ているが,その辺りは後で見ることにしよう.Collatz12146.ZELも描画できた.Collatz4000X.ZELというのを開いてみよう.多分,これが手元のサンプルでは最大級ではないかと思う.このサンプルは12243点,結婚数10170なので,Collatz12146よりも少しだけ大きい.⇒レッドラインオーバーになってしまった.ただし,現行ではレッドラインを30回に引いているので,もう少し大きくすれば多分パスできると思う.⇒いや,ダメだ.やはり,まだオーバーフローがどこかで生じているのだろう.⇒フォントサイズを1/5まで縮小して描画できるようになった.この関係を定式化しなくてはならない.
フォントサイズを極限の1×1まで縮小しても作図可能であることは確認できた.三極検定ではこの極端な設定で計算し,事後に実際のフォントサイズを割り当てるということができれば最善なのだが,可能だろうか?そうなれば,名実ともに「論理フォント」と呼ぶに相応しい位置付けになるのだが…しかし,実サイズに戻したときにもオーバーフローのリスクはあり,また,再計算が収束しないという可能性もあるので,現行論理の延長上で収めるというのが妥当な線かもしれない…
極小反例サンプル.zelが描画できた!
1931点の53直系血族図.ZELは22回でループを脱出している.6165点のCollatz3900-6165.ZELは24回で描画できる.これが今のところ描画可能な最大サンプルだ.12146点のCollatz12146.ZELなどでは,結婚点不一致が20点近くまで減少した後,増加に転じその後は,ほぼ単調に増加し続けるような動作になっている.現行では結婚点が一致している場合には無条件でセグメントをマージするようになっている.セグメント検定に何か致命的な瑕疵があることは間違いないように思われるが,それを突き止めるのは至難の業だ.どうすればよいのだろう?
ともかく,ブレークして一度描画させてみることにしよう.極小反例サンプルを開いてみる.
▲極小反例サンプル.zelをレッドラインオーバーで開いた後,ズーム倍率を切り替えようとして例外が発生した.
CDC::_LPtoDPでエラーが発生している模様だ.レンジオーバーの可能性もある.矩形の右座標が2995305になっている.16進で0x2DB469だ.その前にポイント座標で83869328=0x4FFBEいう値が入っている.POINT という構造体はLONGで構成されている.LONG=longであるとすると,32ビットしかない.しかし,INT_MAXは2147483647で0x7FFFFFFFまでカバーしていることになっている.
TREEVIEW::LimitSizeOverではLIMITSIZE=0x7D00と見ている.ただし,現在はこの関数は呼び出されていない.SIZEOVERAUTOZOOMOUTをオンにしてビルドしてみよう.
!解けた!極小反例サンプル.zelが描画できた!
どうも,どこかでレンジオーバーが置きていたもののように思われる.フォントサイズを強制的に四分の一に縮小して描画できた.ただし,100%までズームすると,かなり悲惨なことになってしまう.
フォントの描画関数の方は調整していないので,こうなってしまうのは仕方ない.また,100%と言っても,1/4の縮小図面の100%なので,通常の見慣れたサイズで表示されるという訳ではない.ともかく,何らかの方法で折り合いを付けなくてはならないが,それも結構手間が掛かりそうだ.というか,こういう巨大サンプルをどう扱うかという基本的な課題についてもう少し考察を深める必要がある.それでも,ともかく描画できるようになったということは喜ばしいことだ.
タイトル行にファイル名が表示されない
やはり,不良の元凶はセグメント検定ということではないだろうか?
▲主画面のタイトル行にファイル名が表示されない.⇒系図タイトルの決定関数がZ.BaseRefnumが未定のうちに呼び出されている.というか,設定されていない.⇒mUpdateBaseRefnumという関数が呼び出されていない.⇒UpdateMaxCardでは,事前にZ.mMaxRecordCountを実行している.⇒mMaxRecordCountの冒頭でmUpdateBaseRefnumを実行するようにして表示されるようになった.いつ頃からこのような状態になっていたのか分からないが,どこか不用意に削除してしまっていたのではないだろうか?
▲BUG3000-1470.ZEL.ZELで三極検定レッドラインオーバーが起こる.BUG3000-1470.ZEL,BUG3000-1471.ZELは正常動作している.このサンプルは1469点で,結婚が0と表示され,画面にはカード1点しか表示されない.不審なのは,三極検定でレッドラインオーバーが起きているという点だ.⇒REDLINEに1という数が入っている.⇒この値は有効な結婚数+1に設定されている.これでは無理だろう.⇒最小値を8とするように修正した.これでエラーなしで描画できるようになったが,1469点登録されているのに1点しか出ないというのはおかしい.⇒親族図になっていた.全体図に切り替えて描画できた.⇒結婚数は1007になっている.最初にカード1枚しか表示されないときは,表示数=1, 結婚数=0となっているので数字は合っている.
この辺りのファイルはBUGが頭に付いているように,当初は障害が発生していたはずなので,(いつの間にか)解消しているということになる.現状では今のところ反例サンプルとして残っているのは,Collatz12146.ZELだけになっているように思われる.