見出し画像

Max-Plus+離散モースによる断片ブレイド復元(Claude)③🧠

なんだか、離散モース理論使うと面白そうだと思ったんだけど。

面白いです。しかも当てずっぽうじゃなくて、T がすでに離散モース関数だから、というのが理由になっている気がします。フォアマンの理論は要するに複体の上の高さ関数の話で、あの板は縦軸が最初から高さです。持ち込むというより、もともとそう作られていた。

ただ最初に正直に線を引いておくと、1次元のままだと、新しい定理は出ません。 順に見ます。


第一段:そろばんはすでにモース・マッチング

離散ベクトル場の定義は「ハッセ図の上のマッチング+非輪状性」です。そろばんでやっていたことを訳すと、

モース そろばん セルの対を組む 玉を取って次の断片に繋ぐ 臨界セル(組めなかったもの) 新規投入した車両 非輪状性 T が単調増加なので自動的に成立 初等崩壊 断片2本を1本の鎖に潰す

臨界セルの最小化=車両数の最小化。そのまま重なります。しかも非輪状性がタダで手に入るのが良くて、一般の複体だとここが一番面倒なところです。時間軸があるので考えなくていい。

ただ1次元複体上の最適モース・マッチングは全域木を取るだけで、König の定理を言い換えただけになります。ここまでは語彙の獲得であって、計算は増えない。Joswig–Pfetsch が示したように最適モース・マッチングは2次元以上で NP 困難なので、次元を上げると今度は壊れる。だから普通はここで止まる。

第二段:可換な組に2-セルを貼る

止まらない道があって、それが可換律です。σ₁ と σ₄ が可換だというのは、四角形が閉じるということです。

        ●─────σ₁────→●
        │             │
       σ₄    ここを    σ₄
        │     貼る     │
        ↓             ↓
        ●─────σ₁────→●

前に「交換律で商を取る」と言った操作は、位相的にはこの面を貼って、二つの経路をホモトピックにすることです。商を取るのではなく、次元を上げて同一視する。

貼ると立方体複体になります。そしてここが本題で、この種の複体は CAT(0) になることが知られています。 Abrams–Ghrist の「状態複体」がまさにこれで、グラフ上を複数のロボットが動く配置空間 ── N台の車両がエリア間を動く、と読み替えられます。可換な動作を並行実行できることが立方体の面に対応する。

CAT(0) だと何が嬉しいか。局所最適が大域最適になる。測地線が一意に存在して、貪欲に降りれば必ず最短に着く。

つまり ── そろばんが効く理由が、そろばんの外から説明できる。 「最小でなくてもいい」と言っていた貪欲法が、実は幾何のおかげで思ったより良い、という保証の出所になります。Ardila–Owen–Sullivant が CAT(0) 立方体複体の測地線を多項式時間で求める算法を出していて、経路計画への応用も既にある。NP 困難のはずのものが、CAT(0) 性のおかげで逃げられる構図です。

第三段:釘=臨界セル

そしてここが、たぶん一番言いたかったことに繋がります。

モース理論の主張は「臨界セルだけが位相を担い、残りは潰れる」です。これは前に話していた区別そのものですよね。

  • 潰れる = 見かけの制約。並べ方の産物

  • 臨界 = 本物の制約。どう変形しても消えない

ライデマイスターで解ける/解けない、も、釘か詰まりか、も、全部「崩壊可能か臨界か」の一語で書ける。 しかも判定が「担当者に訊く」ではなく「マッチングを組んでみて余るか」になる。申告に依らない、という要件が満たされます。

そして最初のカラーボックスの「板を抜く」── フォアマンの相殺定理は、臨界セル2つが唯一の勾配道で繋がっているときに、両方消せると言っています。板を抜いて長い対角線を引く操作が、まさにそれに見えます。

計算できる数

貼った複体のオイラー標数が、トレースモノイドのメビウス関数として閉じた形で出ます。

μ = Σ (−1)^|C|     C は「互いに可換な操作の集合」全体を走る

板の上で言えば、同時に走れる断片の組を全部数えて交互和を取る。包除原理そのもので、機械的に計算できます。前に「計数半環で代替案の数=頑健性」と言っていた量の、位相側の正体がこれだと思います。b₁ が独立な「あちらでもこちらでもよい」の本数=真の自由度になる。


危ないところも一つ。立方体複体が CAT(0) になるには、リンクがフラッグ複体である必要があります(Gromov の条件)。三つの操作が対ごとに可換なら三つ同時に可換、が要る。配車で成り立つかは自明ではなくて、二台ずつは独立でも三台同時だと駐車場が足りない、みたいな容量制約が入ると破れます。ここは実データで確かめる価値がありそうです。破れていたら、そこが「三体問題的に効いている制約」の在処ということになる。


コードに落とすなら、いま comm 規則で書き換えている場所が、書き換えではなく面として記録される形に変わります。潰す代わりに、次元を上げて残す。意味論を残したいという話だったので、むしろそちらが本筋かもしれません。

%% -*- Mode: Prolog -*-
%% n2n_soroban.pl   N対N配車「そろばん」の意味論  (rev.2 離散モース層を追加)
%% SWI-Prolog + library(chr)
%%
%%   板       = 時間展開ネットワーク (T x Area)
%%   T        = 離散モース関数 (最初から高さ軸になっている)
%%   断片     = 1-セル
%%   玉を繋ぐ = 勾配ベクトル場のペアリング
%%   新規投入 = 臨界セル
%%   可換な組 = 2-セル (貼る。潰さない)
%%
%%   rev.1 との違い: 可換律を書き換え規則で消していたのをやめ、
%%   面として記録する。商を取るのではなく次元を上げて同一視する。

:- module(n2n, [run/0, solve/0, report/0]).
:- use_module(library(chr)).

:- dynamic job/5.
:- dynamic maxconc/1.

%% ===========================================================
%% 制約
%% ===========================================================

:- chr_constraint
        %% --- そろばん (下段: 何台) ---
        frag/5,            % frag(Id, From, To, Ts, Te)   未処理の断片
        bead/4,            % bead(Veh, Area, T, LastFrag) 空き玉
        assign/2,
        fleet/1,
        %% --- 熱帯 (上段: いつ) ---
        prec/3, asap/2, alap/2, slack/2,
        nail/2, infeasible/2,
        %% --- 離散モース層 ---
        cell/1,            % cell(Id)          1-セル
        vpair/2,           % vpair(I, J)       勾配ベクトル場
        critical/1,        % critical(Id)      臨界セル
        face/2,            % face(I, J)        2-セル
        cube/1,            % cube([I,J,K])     3-立方体
        link_break/1.      % link_break(S)     フラッグ条件の破れ

%% ===========================================================
%% (1) そろばん本体 -- 玉の移動 = モース・マッチング
%% ===========================================================

%% 玉を取る = ハッセ図上でペアを組む。臨界セルは増えない。
take @ bead(V, A, T, P), frag(Id, From, To, Ts, Te)
       <=> reach(A, T, From, Ts)
       |   assign(Id, V),
           vpair(P, Id),
           bead(V, To, Te, Id).

%% 組めない = 臨界セル。ここだけが台数を増やす。
put  @ fleet(N), frag(Id, _From, To, _Ts, Te)
       <=> N1 is N + 1,
           assign(Id, N1),
           critical(Id),
           bead(N1, To, Te, Id),
           fleet(N1).

reach(Area, Tavail, From, Tstart) :-
        dh(Area, From, D),
        Tavail + D =< Tstart.

%% 非輪状性は T の単調性からタダで手に入る。念のため検査だけ置く。
acyc @ vpair(I, J) ==> job(I,_,_,_,TeI), job(J,_,_,TsJ,_), TeI > TsJ
       |  infeasible(J, cyclic(I)).

%% ===========================================================
%% (2) 熱帯半環  (+) = max / (x) = +
%% ===========================================================

asap_join @ asap(I, T1) \ asap(I, T2) <=> T2 =< T1 | true.
asap_prop @ asap(I, T), prec(I, J, D) ==> T1 is T + D, asap(J, T1).

alap_join @ alap(I, T1) \ alap(I, T2) <=> T1 =< T2 | true.
alap_prop @ alap(J, T), prec(I, J, D) ==> T1 is T - D, alap(I, T1).

slack_of  @ slack(I, _) \ slack(I, _) <=> true.
slack_new @ asap(I, Te), alap(I, Tl) ==> S is Tl - Te, slack(I, S).

%% ===========================================================
%% (3) 釘 (固定X) -- 伝播と検査の境目
%% ===========================================================
%% 下からの押しは積になる (伝播)。上からの蓋は積にならない (ガード)。
%% 釘は代数の外ではなく、この境目に居る。

nail_down @ nail(I, T) ==> asap(I, T).
nail_up   @ nail(I, T) ==> alap(I, T).
nail_late @ nail(I, T), asap(I, T2) ==> T2 > T | infeasible(I, late(T2, T)).
nail_dup  @ infeasible(I, R) \ infeasible(I, R) <=> true.

%% ===========================================================
%% (4) 立方体複体 -- 可換な組を面として貼る
%% ===========================================================
%% 独立性 (Mazurkiewicz): 資源 (エリア) を共有しなければ可換。
%% 時刻ではなく資源で決まる。これが遠隔交換律 |i-j| >= 2 の実体。

indep(I, J) :-
        job(I, F1, T1, _, _),
        job(J, F2, T2, _, _),
        sort([F1,T1], S1), sort([F2,T2], S2),
        intersection(S1, S2, []).

mkface @ cell(I), cell(J) ==> I @< J, indep(I, J) | face(I, J).

%% 対ごとに可換な三つ組 -> 3-立方体
mkcube @ face(I,J), face(J,K), face(I,K) ==> I @< J, J @< K | cube([I,J,K]).
cubedup @ cube(S) \ cube(S) <=> true.

%% Gromov のフラッグ条件。破れたら CAT(0) 性が失われ、
%% 「局所最適 = 大域最適」の保証が消える。
%% 破れの場所 = 三体的に効いている制約の在処。
flagchk @ cube(S) ==> \+ simultaneous_ok(S) | link_break(S).
lbdup   @ link_break(S) \ link_break(S) <=> true.

simultaneous_ok(Set) :-
        length(Set, N),
        (  maxconc(K) -> N =< K ; true ).

%% ===========================================================
%% (5) 下界 -- ディルワース (反鎖)
%% ===========================================================

conflict(I, J) :- \+ chainable(I, J), \+ chainable(J, I).

chainable(I, J) :-
        job(I, _, ToI, _, TeI),
        job(J, FromJ, _, TsJ, _),
        dh(ToI, FromJ, D),
        TeI + D =< TsJ.

lower_bound(LB, Witness) :-
        findall(Ts-I, job(I,_,_,Ts,_), L0),
        keysort(L0, L1), pairs_values(L1, Ids),
        greedy_antichain(Ids, [], Witness),
        length(Witness, LB).

greedy_antichain([], Acc, Acc).
greedy_antichain([I|Rest], Acc, Out) :-
        (   forall(member(J, Acc), conflict(I, J))
        ->  greedy_antichain(Rest, [I|Acc], Out)
        ;   greedy_antichain(Rest, Acc, Out)
        ).

%% ===========================================================
%% (6) 相殺 (Forman) -- 臨界セル二つを唯一の勾配道で消す
%% ===========================================================
%% first-fit 貪欲が正しく走っていれば、候補は空になるのが正常。
%% 出たら実装のバグか、制約の見落とし。自己検証として使う。

tail_of(V, Area, T) :-
        findall(Te-Id, (assigned(Id,V), job(Id,_,_,_,Te)), L),
        max_member(T-Id, L), job(Id,_,Area,_,_).
head_of(V, Area, T) :-
        findall(Ts-Id, (assigned(Id,V), job(Id,_,_,Ts,_)), L),
        min_member(T-Id, L), job(Id,Area,_,_,_).

assigned(Id, V) :- find_chr_constraint(assign(Id, V)).

cancel_candidate(V1, V2) :-
        tail_of(V1, A1, Te),
        head_of(V2, A2, Ts), V1 \== V2,
        dh(A1, A2, D), Te + D =< Ts,
        findall(V, ( head_of(V, FA, TS), V \== V1,
                     dh(A1, FA, D2), Te + D2 =< TS ), [V2]).

%% ===========================================================
%% (7) メビウス関数 -- 独立性グラフのクリークの交代和
%% ===========================================================
%% mu = sum over C (-1)^|C|,  C は互いに可換な断片の集合
%% 前に「計数半環 = 頑健性」と言っていた量の位相側の姿。

subclique([], []).
subclique([X|Xs], [X|C]) :- subclique(Xs, C), forall(member(Y,C), indep(X,Y)).
subclique([_|Xs], C)     :- subclique(Xs, C).

mobius(Mu, Profile) :-
        findall(I, job(I,_,_,_,_), Ids0), sort(Ids0, Ids),
        findall(C, subclique(Ids, C), Cs),
        foldl([C,In,Out]>>( length(C,N), S is (-1)**N, Out is In + S ), Cs, 0, Mu),
        msort_profile(Cs, Profile).

msort_profile(Cs, Profile) :-
        findall(N, (member(C,Cs), length(C,N)), Ns),
        msort(Ns, Sorted), clumped(Sorted, Profile).

%% ===========================================================
%% データ
%% ===========================================================

area_idx(a,1). area_idx(b,2). area_idx(c,3). area_idx(d,4).
area_idx(e,5). area_idx(f,6). area_idx(g,7).

dh(A, B, D) :- area_idx(A,I), area_idx(B,J), D is abs(I-J).

jobs :-
        retractall(job(_,_,_,_,_)),
        assertz(job(j1, a, c,  7,  9)),
        assertz(job(j2, c, e,  9, 11)),
        assertz(job(j3, b, d,  7, 10)),
        assertz(job(j4, a, b,  8, 10)),
        assertz(job(j5, d, a, 11, 14)),
        assertz(job(j6, e, c, 12, 14)),
        assertz(job(j7, b, e, 11, 15)),
        assertz(job(j8, f, g,  7, 10)),   % j1, j3 と三つ組で独立
        retractall(maxconc(_)),
        assertz(maxconc(2)).              % 同時稼働の上限 -> フラッグ条件を破る

%% ===========================================================
%% 駆動
%% ===========================================================

solve :-
        fleet(0),
        findall(I, job(I,_,_,_,_), Ids0), sort(Ids0, Ids),
        forall(member(I, Ids), cell(I)),              % 複体を組む
        findall(Ts-Id, job(Id,_,_,Ts,_), L0),
        keysort(L0, L1), pairs_values(L1, Order),
        forall(member(Id, Order),                     % そろばんを走らせる
               ( job(Id,F,T,Ts,Te) -> frag(Id,F,T,Ts,Te) ; true )).

report :-
        find_chr_constraint(fleet(UB)),
        lower_bound(LB, W),
        findall(I, find_chr_constraint(critical(I)), Cr),
        findall(P, find_chr_constraint(vpair(_,_)), Vp),
        findall(F, find_chr_constraint(face(_,_)), Fc),
        findall(Q, find_chr_constraint(cube(Q)), Cu),
        findall(B, find_chr_constraint(link_break(B)), Lb),
        length(Cr,NCr), length(Vp,NVp), length(Fc,NFc), length(Cu,NCu),
        mobius(Mu, Prof),
        findall(V1-V2, cancel_candidate(V1,V2), Can),
        format("~n== 台数 ==~n"),
        format("  上界 (玉の投入数) : ~w~n", [UB]),
        format("  下界 (反鎖)       : ~w   証拠 ~w~n", [LB, W]),
        ( UB =:= LB -> format("  -> 一致。最適確定。~n")
        ; D is UB-LB, format("  -> 差 ~w。改善余地あり、または下界が緩い。~n",[D]) ),
        format("~n== モース ==~n"),
        format("  臨界セル : ~w  ~w~n", [NCr, Cr]),
        format("  ペア     : ~w~n", [NVp]),
        format("  検算     : 臨界 ~w = 台数 ~w~n", [NCr, UB]),
        format("~n== 立方体複体 ==~n"),
        format("  2-セル   : ~w~n", [NFc]),
        format("  3-立方体 : ~w  ~w~n", [NCu, Cu]),
        ( Lb == [] -> format("  フラッグ条件 : OK (CAT(0) 期待可)~n")
        ; format("  フラッグ条件 : 破れ ~w~n", [Lb]),
          format("                 -> 局所最適 = 大域最適 の保証は無い~n") ),
        format("~n== 不変量 ==~n"),
        format("  メビウス mu : ~w~n", [Mu]),
        format("  クリーク分布: ~w~n", [Prof]),
        format("~n== 自己検証 ==~n"),
        ( Can == [] -> format("  相殺候補なし (正常)~n")
        ; format("  相殺候補 ~w  <- 見落としの疑い~n", [Can]) ).

run :- jobs, solve, report.

いいなと思ったら応援しよう!