天赐范式第205天:断的是规模不是基因流——退出率 × 补位扫描

断的是规模不是基因流------退出率 × 补位扫描

个体按退出率 λ 离场(基因不再参与复制)× 补位{不补, 延迟 5 代} × 5 seed

摘要

退出抽检进 204 架构(λ=0 档逐位复现 204a 主实验):不补档下任何非零 λ 规模单调衰减(H1a 支持),λ=0.10 在第 68 代归零;延迟 5 代补位把"必灭"变成非零平台 190/178/169(H1b 支持),与解析式 N(1−λ·delay) 逐档吻合。规模 ≥30 时亲子 θ 相关 0.998;规模 <30 后降至 0.49------传递保真度随规模缩水下滑但未归零。断的是规模不是基因流:存续不取决于退出多弱,取决于有没有补位。

一、接 204:两条通道

204 两篇答的是遗传通道(θ 怎么传)。两篇 Future Work 第 2 项都写着"退出率 × 基因流",204-2 文末还把这个问题留给读者------本篇兑现。

一个架构事实先摆清楚:204 的锦标赛 winner 整格覆盖已经隐含生死(败者消失=死、后代占据=繁殖同时发生)。真正的缺口是:舞台永远自动填满(N 恒定 200),从没有任何机制让格子空出来。火种轴第三件"种群可存续"因此从未被测过------没有缩水,就没有断裂。

本篇开"生死+存续通道":个体按退出率 λ 逐个抽检离场(离场=彻底消失+基因不再参与任何复制),格子空置,种群可以缩水。"火种"= 种群规模与基因流在扰动下不断裂的操作化表述(比喻)。

命名纪律:退出率(非"死亡率")、种群归零(非"灭绝")、补位(非"出生")。零文献考场:本篇不引任何文献当判分标准。

二、机制:退出模块

更新顺序(钉死):退出抽检(代开始前)→ 参与判定 → γ 一生内学习 → 刺激更新 → 锦标赛整套复制 → 补位(若开)。θ 遗传沿用 204 同一锦标赛(β+θ 整套复制,各自突变)。

退出机制三个钉死点:

  1. λ=0 不跑抽检循环------退出模块关闭时随机流与 204a 完全一致(验收锚点的前提)
  2. 空格从候选池剔除;候选池只剩自己 → winner=自己(自体复制+突变)------自体复制率本身是可报读数(候选池萎缩的代价)
  3. 补位规则写死:离场后固定 5 代,空格由 Moore 邻域中 q_eff 最大的活格复制版填充

三、验收锚点

λ=0 档与 204a 主实验(σ_θ=0.02, γ=0.05)逐位对比,六项 5/5:betas / participate / theta / θ快照 / 谷深 / 逐代 n_act 全 √------退出模块关闭时随机流完全一致。

四、不补档:纯缩水(H1a)

λ 末态规模均值 半衰期 归零代次 自体复制率 相关(≥30段) 相关(<30段)
0.01 74 70.6 未归零 19.7% 0.995 ---(未跌破30)
0.02 27 34.1 未归零 27.3% 0.997 ---(样本不足)
0.03 8 20.3 未归零 32.4% 0.998 0.488
0.10 0 5.8 第 68 代 35.4% 0.998 0.500

亲子 θ 相关按规模分段:≥30 段(缩水前)5 seed 均值 0.998;<30 段(缩到个位数时)λ=0.03 档 0.488(逐 seed 0.297/0.345/0.352/0.997/0.450)------传递保真度在深缩水区降至约一半,但未归零。

H1a(任何非零 λ 规模单调衰减,无稳定区间):支持 ------20 条轨迹(4λ × 5 seed)逐代全部单调,无一回升。注意单调本身是机制定义的必然(不补档下 alive 只能 True→False 单向变化),读数的信息量在无稳定平台、必归零------λ=0.01 是纯指数衰减(0.99^100≈69/200 对得上);λ=0.10 在第 68 代归零。

自体复制率随 λ 单调上升(12.8%→35.4%):候选池萎缩的必然代价------空格越多,"只剩自己可选"的个体越多。

五、补位档:非零平台(H1b)

λ 平台均值±std(末 40 代) 补位次数 亲子 θ 相关 谷深(存活)
0.01 190±3 176 0.993 0.000
0.02 178±4 348 0.994 0.000
0.03 169±5 507 0.994 0.000

H1b(非零平衡平台,平台随 λ 下降):支持 ------平台 190/178/169 随 λ 下降。延迟 5 代补位把"必灭"变成动态平衡:退出与补位对冲,平台有解析式------平台 ≈ N·(1 − λ·delay),缺口 ≈ λ·N·delay:

λ 平台扫描值 解析 N(1−5λ) 缺口扫描值 解析 λN·delay
0.01 190 190 10 10
0.02 178 180 22 20
0.03 169 170 31 30

逐档吻合(±2 人)------"退出×补位对冲"的机制自洽:每代退出 λ·N 人、5 代后补回,稳态缺口就是 5 倍的每代退出数。

补位档谷深(存活口径)全 0.000------补位救的是规模,不是分化(互参见 205-2)。

六、半衰期:扫描值 vs 解析

档 半衰期扫描值 解析 0.693/λ 偏离
λ=0.01 70.6 69.3 +1.8%
λ=0.02 34.1 34.6 -1.6%
λ=0.03 20.3 23.1 -12.0%

小 λ 下扫描值与解析近似高度吻合(±2%)------纯指数衰减的读数免费验证。λ=0.03 偏离 -12%,归因于半衰期的首次穿越定义+离散代际抽样(线性插值在快衰减段放大离散误差),非机制性偏离------纯退出对照(无复制、只有退出抽签)同档半衰期 22.8,与含复制版 20.3 同量级,复制(含自体复制)不改变衰减速度。

七、断的形式:断的是规模不是基因流

主结论并排读数(λ=0.03 不补档):末态存活均值 8 人 ;亲子 θ 相关按规模分段------≥30 段 0.998,<30 段 0.488。

两个分段合起来读:规模缩到 5% 人口,θ 的代际传递保真度从 0.998 滑到 0.49------下滑但未断。基因流只在规模归零处才彻底断(λ=0.10 归零档)。这给"火种"一个分岔的定义:规模的火种靠补位(H1b),基因流的火种靠不归零------但保真度本身也随规模缩水打折。

一个诚实注:亲子 θ 相关这个指标主要反映"突变步长 vs 群体 θ 宽度"的比值(理论式 w/√(w²+σ_θ²)),不直接测群体层面的基因流连通性------它答的是"父子像不像",不是"全村是不是一家"。λ 越大幸存者 θ 分布越宽,相关反而略升,正说明该指标对规模不敏感。跨格谱系混合度才是连通性的直接测法,留 Future Work。

八、小样本禁判纪律

λ=0.03 不补档 5 seed 谷深均值 0.400,落在预注册的 0.4-0.6 边缘带------按预注册条件触发扩样(seed 47-51):

seed 末态存活 谷深 hist 形态
42 5 0.000 全挤 bin9
43 9 0.000 全挤 bin9
44 8 0.000 bin8+9 单峰
45 11 1.000 多数 bin9 + 3 个散点
46 8 1.000 多数 bin9 + 3 个散点
47 9 1.000 多数 bin9 + 3 个散点
48 10 1.000 多数 bin9 + 3 个散点
49 11 1.000 多数 bin9 + 3 个散点
50 9 0.000 bin8+9 连成单峰
51 9 1.000 多数 bin9 + 3 个散点

10 seed 均值 0.600------但逐 seed 末态存活 5-11 人,全部 <30。扩样增加的是同分布噪声样本,不增加单 run 判力:hist 形态=多数挤 bin9 + 1-3 个散点,谷深 1.0/0.0 由散点位置决定(小样本散点噪声)。

结论:末态存活 <30 人时谷深判据无判力(禁判)。λ=0.03 档不是边缘分化读数,是样本不足。 这与假低口径是同族陷阱------一个用死格堆 bin0 造假谷,一个用幸存者散点造假峰。

九、诚实边界

Limitations:

  1. 退出率为常数(不随规模/环境反馈),未扫时变退出
  2. 补位规则仅一档(延迟 5 代、邻域 q_eff 最大填充)
  3. 亲子相关为分段合并口径(剔自体),非逐代;该指标不直接测群体基因流连通性(见 §七诚实注)
  4. 补位复活个体的参与频率分母仍为完整 20 代(其存活代数 <20,频率被系统性低估)------补位档谷深 0.000 结论不受影响,但精确读数须按存活代数换分母
  5. 单任务、5 seed、教学级

Future Work:

  1. 补位规则的其他形式(退出者遗产、变异步长补位)
  2. 退出率与规模的反馈耦合(密度依赖退出)
  3. 跨格谱系混合度------直接测基因流连通性的指标(亲子相关只测传递保真度)

十、总结与下一篇

  • λ=0 档逐位复现 204a 主实验:六项 5/5
  • 不补档全档单调衰减(H1a 支持);λ=0.10 第 68 代归零
  • 补位档平台 190/178/169(H1b 支持),与解析式 N(1−λ·delay) 逐档吻合
  • 断的是规模不是基因流:≥30 段相关 0.998、<30 段 0.49(下滑未断),归零才断流

204 答遗传通道(θ 怎么传),本篇开生死+存续通道------存续不取决于退出多弱,取决于有没有补位。

下一篇的问题:退出压力下火种没断(补位平台成立),那分化被逼出来了吗?------205-2《分化没被逼出来》回答。

代码附录

python 复制代码
# -*- coding: utf-8 -*-
"""
天赐范式 第205天 第一篇
断的是规模不是基因流

PID: TC-205A-V3.3.43.0
V3.3.43.0 · 2026-10-24

接 204 两篇 Future Work:退出率 × 基因流。
"火种"= 种群规模与基因流在扰动下不断裂的操作化表述(比喻)。
架构事实:204 锦标赛 winner 整格覆盖已隐含生死;本篇新增的是
舞台不再自动填满------个体退出后格子空置,种群可以缩水。
"""
import sys
import math
import random
import statistics

sys.stdout.reconfigure(encoding="utf-8")

sys.path.insert(0, r"C:\Users\Administrator\Desktop\20261021")
from tianci_202a import (analyze_run, valley_depth, TRACK_WINDOW, N_GEN_202,
                          SEEDS, PHI, RHO, QUAD_ENV, VALLEY_STRONG,
                          VALLEY_WEAK, SNR_THRESHOLD)

sys.path.insert(0, r"C:\Users\Administrator\Desktop\20261023")
from tianci_204a import run_frtm_gene

sys.path.insert(0, r"C:\Users\Administrator\Desktop\20261019")
from tianci_200b import (GRID_W, GRID_H, N, N_GEN, SIGMA_V, TOURNAMENT_K,
                          N_BUCKETS, true_beta, get_quadrant, moore_neighbors,
                          get_bucket, shannon_entropy, clamp, bar, md_table)

GAMMA_MAIN = 0.05
THETA_MIN = 0.01
SIGMA_THETA = 0.02
LAMBDA_SCAN = [0.0, 0.01, 0.02, 0.03]
LAMBDA_ZERO_EXTRA = 0.10
FILL_DELAY = 5


def run_frtm_exit(seed, gamma, sigma_theta, lam=0.0, fill_delay=None,
                  n_gen=N_GEN_202):
    """205 退出模块(两篇内嵌同一份,禁互相 import)。
    机制:代开始前按退出率 λ 抽检离场(离场=彻底消失+基因不再参与复制);
    空格从候选池剔除;候选池只剩自己 → 自体复制+突变。
    更新顺序(钉死):退出抽检 → 参与判定 → γ 一生内学习 → 刺激更新
    → 锦标赛整套复制(β+θ 同 tournament,各自突变)→ 补位(若开)。
    λ=0 时不跑抽检循环------随机流与 204a run_frtm_gene 逐位一致(验收锚点)。
    补位:离场后固定 fill_delay 代,空格由 Moore 邻域中 q_eff 最大的
    活格复制版填充(当代值+各自突变)。
    返回九元组:history, betas, thetas, participate_counts, alive,
    theta_snaps, exit_stats, fill_count, scale_traj。"""
    random.seed(seed)
    betas = [random.uniform(0.0, 1.0) for _ in range(N)]
    thetas = [random.uniform(0.0, 1.0) for _ in range(N)]
    s_stim = [0.0, 0.0, 0.0, 0.0]
    history = []
    participate_counts = [0] * N
    alive = [True] * N
    death_gen = [None] * N
    theta_snaps = []
    self_copy_count = 0
    copy_total = 0
    parent_theta = []
    child_theta = []
    parent_gen_alive = []
    fill_count = 0
    scale_traj = []
    track_start = n_gen - TRACK_WINDOW + 1

    for gen in range(n_gen + 1):
        if lam > 0:
            for i in range(N):
                if alive[i] and random.random() < lam:
                    alive[i] = False
                    death_gen[i] = gen

        n_alive = sum(alive)
        scale_traj.append(n_alive)

        if gen % 10 == 0:
            live_th = [thetas[i] for i in range(N) if alive[i]]
            if len(live_th) > 1:
                theta_snaps.append((gen, statistics.mean(live_th),
                                    statistics.stdev(live_th)))
            else:
                theta_snaps.append((gen, live_th[0] if live_th else 0.0, 0.0))

        q_base = [0.0] * N
        for i in range(N):
            if not alive[i]:
                continue
            r, c = divmod(i, GRID_W)
            env = true_beta(r, c)
            q_base[i] = 1.0 / (1.0 + (betas[i] - env) ** 2)

        n_act = [0, 0, 0, 0]
        q_eff = [0.0] * N
        participated = [False] * N
        for i in range(N):
            if not alive[i]:
                continue
            r, c = divmod(i, GRID_W)
            q = get_quadrant(r, c)
            s = s_stim[q]
            if s > 0 and thetas[i] > 0:
                p_part = s * s / (s * s + thetas[i] * thetas[i])
            else:
                p_part = 0.0
            if random.random() < p_part:
                q_eff[i] = q_base[i] * (1.0 + RHO)
                n_act[q] += 1
                participated[i] = True
            else:
                q_eff[i] = q_base[i]

        if gamma > 0:
            for i in range(N):
                if participated[i]:
                    thetas[i] = max(THETA_MIN, thetas[i] - gamma)

        if gen >= track_start:
            for i in range(N):
                if participated[i]:
                    participate_counts[i] += 1

        alive_per_quad = [0, 0, 0, 0]
        for i in range(N):
            if alive[i]:
                alive_per_quad[get_quadrant(*divmod(i, GRID_W))] += 1
        part_rates_live = [
            n_act[q] / alive_per_quad[q] if alive_per_quad[q] > 0 else 0.0
            for q in range(4)]
        part_rates_low = [n_act[q] / (N // 4) for q in range(4)]

        p_total = sum(q_eff)
        mean_b = sum(betas) / N
        bucket_c = [0] * N_BUCKETS
        for b in betas:
            bucket_c[get_bucket(b)] += 1
        n_occ = sum(1 for cnt in bucket_c if cnt > 0)
        ent = shannon_entropy(bucket_c, N)

        history.append({
            'gen': gen, 'p': p_total, 'mean_b': mean_b,
            'n_occ': n_occ, 'entropy': ent,
            's_stim': tuple(s_stim), 'n_act': tuple(n_act),
            'part_rates': tuple(part_rates_low),
            'part_rates_live': tuple(part_rates_live),
            'n_alive': n_alive,
        })

        if gen == n_gen:
            break

        for q in range(4):
            s_stim[q] = max(0.0, s_stim[q] + QUAD_ENV[q] - PHI * n_act[q])

        new_betas = [0.0] * N
        new_thetas = [0.0] * N
        for i in range(N):
            if not alive[i]:
                new_betas[i] = betas[i]
                new_thetas[i] = thetas[i]
                continue
            r, c = divmod(i, GRID_W)
            candidates = [i]
            for j in moore_neighbors(r, c):
                if alive[j]:
                    candidates.append(j)
            k = min(TOURNAMENT_K, len(candidates))
            sample = random.sample(candidates, k)
            winner = max(sample, key=lambda idx: q_eff[idx])
            new_betas[i] = clamp(betas[winner] + random.gauss(0, SIGMA_V))
            new_thetas[i] = max(THETA_MIN,
                                thetas[winner] + random.gauss(0, sigma_theta))
            copy_total += 1
            if winner == i:
                self_copy_count += 1
            else:
                parent_theta.append(thetas[winner])
                child_theta.append(new_thetas[i])
                parent_gen_alive.append(n_alive)

        if fill_delay is not None:
            for i in range(N):
                if (not alive[i]) and death_gen[i] is not None \
                        and (gen - death_gen[i]) >= fill_delay:
                    r, c = divmod(i, GRID_W)
                    neighbors = [j for j in moore_neighbors(r, c)
                                 if alive[j]]
                    if neighbors:
                        w = max(neighbors, key=lambda idx: q_eff[idx])
                        new_betas[i] = clamp(
                            betas[w] + random.gauss(0, SIGMA_V))
                        new_thetas[i] = max(
                            THETA_MIN,
                            thetas[w] + random.gauss(0, sigma_theta))
                        alive[i] = True
                        death_gen[i] = None
                        fill_count += 1
                        participate_counts[i] = 0

        betas = new_betas
        thetas = new_thetas

    exit_stats = {
        'self_copy_count': self_copy_count,
        'copy_total': copy_total,
        'parent_theta': parent_theta,
        'child_theta': child_theta,
        'parent_gen_alive': parent_gen_alive,
    }
    return (history, betas, thetas, participate_counts, alive, theta_snaps,
            exit_stats, fill_count, scale_traj)


def pearson(xs, ys):
    n = len(xs)
    if n < 2:
        return float('nan')
    mx = sum(xs) / n
    my = sum(ys) / n
    cov = sum((x - mx) * (y - my) for x, y in zip(xs, ys))
    vx = sum((x - mx) ** 2 for x in xs)
    vy = sum((y - my) ** 2 for y in ys)
    if vx <= 0 or vy <= 0:
        return float('nan')
    return cov / math.sqrt(vx * vy)


def exit_readouts(scale_traj, theta_snaps, exit_stats, fill_count):
    """205 新增读数:半衰期(扫描值+解析)/归零代次/平台±std/
    亲子相关(剔自体、存活≥30 代)/自体复制率。"""
    half_life = None
    for g in range(len(scale_traj) - 1):
        n1, n2 = scale_traj[g], scale_traj[g + 1]
        if n1 > N / 2 >= n2:
            half_life = g + (n1 - N / 2) / (n1 - n2)
            break
    zero_gen = None
    for g, n in enumerate(scale_traj):
        if n == 0:
            zero_gen = g
            break
    tail40 = scale_traj[-40:] if len(scale_traj) >= 40 else scale_traj
    platform = statistics.mean(tail40) if tail40 else 0.0
    platform_std = statistics.stdev(tail40) if len(tail40) > 1 else 0.0
    pts = exit_stats['parent_theta']
    cts = exit_stats['child_theta']
    gas = exit_stats['parent_gen_alive']
    xs = [p for p, g in zip(pts, gas) if g >= 30]
    ys = [c for c, g in zip(cts, gas) if g >= 30]
    r_live = pearson(xs, ys) if xs else float('nan')
    xs_lo = [p for p, g in zip(pts, gas) if g < 30]
    ys_lo = [c for c, g in zip(cts, gas) if g < 30]
    r_low = pearson(xs_lo, ys_lo) if len(xs_lo) >= 10 else float('nan')
    r_all = pearson(pts, cts) if pts else float('nan')
    sc_rate = (exit_stats['self_copy_count'] /
               exit_stats['copy_total']
               if exit_stats['copy_total'] else 0.0)
    return {
        'half_life': half_life, 'zero_gen': zero_gen,
        'platform': platform, 'platform_std': platform_std,
        'r_live': r_live, 'r_all': r_all, 'self_copy_rate': sc_rate,
        'fill_count': fill_count,
        'r_low': r_low, 'n_low': len(xs_lo),
        'parent_std': (statistics.stdev(pts) if len(pts) > 1
                       else 0.0),
    }


def dual_valley(participate_counts, alive):
    """谷深两口径:存活口径(主)+ 假低口径(死格当 0 频率入 hist)。"""
    f_live = [participate_counts[i] / TRACK_WINDOW
              for i in range(N) if alive[i]]
    hist_live = [0] * 10
    for fi in f_live:
        hist_live[min(int(fi * 10), 9)] += 1
    v_live, k1l, k2l = valley_depth(hist_live)
    f_all = [participate_counts[i] / TRACK_WINDOW for i in range(N)]
    hist_low = [0] * 10
    for fi in f_all:
        hist_low[min(int(fi * 10), 9)] += 1
    v_low, k1w, k2w = valley_depth(hist_low)
    return {'hist_live': hist_live, 'v_live': v_live, 'k1l': k1l, 'k2l': k2l,
            'hist_low': hist_low, 'v_low': v_low, 'k1w': k1w, 'k2w': k2w}


if __name__ == "__main__":
    bar("天赐范式 第205天 第一篇  断的是规模不是基因流")
    print()

    bar("Part 0  设计")
    print("  接 204 两篇 Future Work:退出率 × 基因流(对读者的公开承诺)。")
    print("  \"火种\"= 种群规模与基因流在扰动下不断裂的操作化表述(比喻)。")
    print("  架构事实:204 锦标赛 winner 整格覆盖已隐含生死(败者消失);")
    print("  本篇新增:舞台不再自动填满------个体退出后格子空置,种群可缩水。")
    print("  退出抽检(代开始前,λ=0 不跑→随机流与 204a 一致)→ 参与判定")
    print("  → γ 学习 → 刺激更新 → 锦标赛整套复制(空格剔除候选池)→ 补位。")
    print(f"  λ∈{LAMBDA_SCAN + [LAMBDA_ZERO_EXTRA]} × 补位{{不补, 延迟{FILL_DELAY}代}}"
          f" × 5 seed;γ={GAMMA_MAIN} σ_θ={SIGMA_THETA}(204 主档)。")
    print("  命名纪律:退出率(非死亡率)、种群归零(非灭绝)、补位(非出生)。")
    print("  零文献考场:本篇不引任何文献当判分标准。")
    print()

    bar("Part 1  验收锚点(λ=0 逐位复现 204a 主实验)")
    print()
    verify_ok = True
    for seed in SEEDS:
        (h1, b1, t1, pc1, al1, sn1, ex1, fc1, st1) = run_frtm_exit(
            seed, GAMMA_MAIN, SIGMA_THETA, 0.0, None)
        h2, b2, t2, pc2, tr2, sn2 = run_frtm_gene(
            seed, GAMMA_MAIN, SIGMA_THETA)
        betas_match = all(abs(b1[i] - b2[i]) < 1e-12 for i in range(N))
        pc_match = pc1 == pc2
        theta_match = all(abs(t1[i] - t2[i]) < 1e-12 for i in range(N))
        snaps_match = (len(sn1) == len(sn2) and
                       all(abs(a[1] - b[1]) < 1e-12 and
                           abs(a[2] - b[2]) < 1e-12
                           for a, b in zip(sn1, sn2)))
        dv = dual_valley(pc1, al1)
        v_match = abs(dv['v_live'] - 0.000) < 1e-9
        traj_match = all(sum(h1[g]['n_act']) == sum(h2[g]['n_act'])
                         for g in range(N_GEN_202 + 1))
        match = (betas_match and pc_match and theta_match and snaps_match
                 and v_match and traj_match)
        if not match:
            verify_ok = False
        print(f"  seed {seed}: betas {'√' if betas_match else '×'}  "
              f"participate {'√' if pc_match else '×'}  "
              f"theta {'√' if theta_match else '×'}  "
              f"snaps {'√' if snaps_match else '×'}  "
              f"谷深 {'√' if v_match else '×'}  "
              f"n_act {'√' if traj_match else '×'}")
    print(f"  → λ=0 逐位复现 204a 主实验:{'√ 通过' if verify_ok else '× 失败'}")
    print()

    bar("Part 2  不补档扫描(λ × 5 seed,纯缩水)")
    print()
    nofill = {}
    for lam in LAMBDA_SCAN + [LAMBDA_ZERO_EXTRA]:
        rows = []
        for seed in SEEDS:
            (h, b, t, pc, al, sn, ex, fc, st) = run_frtm_exit(
                seed, GAMMA_MAIN, SIGMA_THETA, lam, None)
            ro = exit_readouts(st, sn, ex, fc)
            dv = dual_valley(pc, al)
            live_theta = [t[i] for i in range(N) if al[i]]
            row = {
                'seed': seed, 'scale': st, 'ro': ro, 'dv': dv,
                'end_alive': sum(al),
                'theta_end': statistics.mean(live_theta) if live_theta
                else float('nan'),
                'snaps': sn,
            }
            rows.append(row)
        nofill[lam] = rows
        hl_list = [r['ro']['half_life'] for r in rows]
        hl_str = (f"{statistics.mean(hl_list):.1f}"
                  if all(x is not None for x in hl_list)
                  else f"{sum(1 for x in hl_list if x is None)}/5 未达")
        zg_list = [r['ro']['zero_gen'] for r in rows]
        zg_str = (f"{statistics.mean(zg_list):.0f}"
                  if all(x is not None for x in zg_list)
                  else f"{sum(1 for x in zg_list if x is None)}/5 未归零")
        r_list = [r['ro']['r_live'] for r in rows]
        sc_list = [r['ro']['self_copy_rate'] for r in rows]
        end_list = [r['end_alive'] for r in rows]
        print(f"  λ={lam:.2f}: 末态规模均值={statistics.mean(end_list):.0f}"
              f"  半衰期={hl_str}  归零代次={zg_str}")
        rl_list = [r['ro']['r_low'] for r in rows]
        rl_str = (f"{statistics.mean(rl_list):.3f}"
                  if all(not math.isnan(x) for x in rl_list) else "---")
        print(f"    亲子θ相关(剔自体,存活≥30)={statistics.mean(r_list):.3f}"
              f"  <30段相关={rl_str}")
        print(f"    自体复制率={statistics.mean(sc_list):.1%}")
        vl = [r['dv']['v_live'] for r in rows]
        vw = [r['dv']['v_low'] for r in rows]
        judge_rows = [r for r in rows if r['end_alive'] >= 30]
        if len(judge_rows) >= 3:
            print(f"    谷深: 存活口径="
                  f"{statistics.mean([r['dv']['v_live'] for r in judge_rows]):.3f}"
                  f"(可判 {len(judge_rows)}/5,末态存活≥30)"
                  f"  假低口径={statistics.mean(vw):.3f}")
        else:
            print(f"    谷深: 存活口径=样本不足禁判({len(judge_rows)}/5 可判,"
                  f"需≥3)  假低口径={statistics.mean(vw):.3f}")
        print()

    bar("Part 2b  扩样触发检查(预注册:谷深均值落 0.4-0.6 → 扩 10 seed)")
    print()
    v003_all = statistics.mean([r['dv']['v_live'] for r in nofill[0.03]])
    print(f"  λ=0.03 5 seed 谷深均值={v003_all:.3f} 落 0.4-0.6 边缘带"
          f" → 按预注册条件触发扩样(seed 47-51):")
    extra_rows = []
    for seed in [47, 48, 49, 50, 51]:
        (h, b, t, pc, al, sn, ex, fc, st) = run_frtm_exit(
            seed, GAMMA_MAIN, SIGMA_THETA, 0.03, None)
        dv = dual_valley(pc, al)
        extra_rows.append({'seed': seed, 'end_alive': sum(al), 'dv': dv})
    print(f"  10 seed 逐 seed 全表(42-51):")
    base_rows = nofill[0.03]
    for r in base_rows + extra_rows:
        print(f"    seed {r['seed']}: 末态={r['end_alive']}"
              f"  v={r['dv']['v_live']:.3f}"
              f"  hist={r['dv']['hist_live']}")
    all_v = ([r['dv']['v_live'] for r in base_rows] +
             [r['dv']['v_live'] for r in extra_rows])
    print(f"  10 seed 均值={statistics.mean(all_v):.3f}")
    print(f"  逐 seed 末态存活 5-11 人,全部 <30------扩样增加的是同分布")
    print(f"  噪声样本,不增加单 run 判力:hist 形态 = 多数挤 bin9 +")
    print(f"  1-3 个散点,谷深 1.0/0.0 由散点位置决定(小样本散点噪声)。")
    print(f"  结论:末态存活 <30 人时谷深判据无判力(禁判),")
    print(f"  λ=0.03 档不是边缘分化读数,是样本不足。")
    print()

    bar("Part 3  补位档(延迟 5 代 × λ × 5 seed)")
    print()
    filled = {}
    for lam in [0.01, 0.02, 0.03]:
        rows = []
        for seed in SEEDS:
            (h, b, t, pc, al, sn, ex, fc, st) = run_frtm_exit(
                seed, GAMMA_MAIN, SIGMA_THETA, lam, FILL_DELAY)
            ro = exit_readouts(st, sn, ex, fc)
            dv = dual_valley(pc, al)
            live_theta = [t[i] for i in range(N) if al[i]]
            row = {
                'seed': seed, 'scale': st, 'ro': ro, 'dv': dv,
                'end_alive': sum(al),
                'theta_end': statistics.mean(live_theta) if live_theta
                else float('nan'),
            }
            rows.append(row)
        filled[lam] = rows
        pf = [r['ro']['platform'] for r in rows]
        ps = [r['ro']['platform_std'] for r in rows]
        fc_list = [r['ro']['fill_count'] for r in rows]
        r_list = [r['ro']['r_live'] for r in rows]
        sc_list = [r['ro']['self_copy_rate'] for r in rows]
        vl = [r['dv']['v_live'] for r in rows]
        analytic = N * (1 - lam * FILL_DELAY)
        print(f"  λ={lam:.2f}: 平台均值={statistics.mean(pf):.0f}"
              f"±{statistics.mean(ps):.0f}(末 40 代)  "
              f"解析 N(1-λ·delay)={analytic:.0f}  "
              f"补位次数={statistics.mean(fc_list):.0f}")
        print(f"    亲子θ相关={statistics.mean(r_list):.3f}"
              f"  自体复制率={statistics.mean(sc_list):.1%}"
              f"  谷深(存活)={statistics.mean(vl):.3f}")
        print()

    bar("Part 4  半衰期扫描值 vs 解析(0.693/λ)")
    print()
    hl_rows = []
    for lam in [0.01, 0.02, 0.03]:
        hl_list = [r['ro']['half_life'] for r in nofill[lam]]
        if all(x is not None for x in hl_list):
            hl_mean = statistics.mean(hl_list)
            analytic = 0.693 / lam
            hl_rows.append([f"λ={lam:.2f}", f"{hl_mean:.1f}",
                            f"{analytic:.1f}",
                            f"{(hl_mean - analytic) / analytic:+.1%}"])
        else:
            hl_rows.append([f"λ={lam:.2f}", "未达", f"{0.693 / lam:.1f}", "---"])
    md_table(["档", "半衰期扫描值", "解析 0.693/λ", "偏离"],
             hl_rows, "lrrr")
    print()
    print("  纯退出对照(无复制、只有退出抽签,5 seed):")
    for lam in [0.01, 0.02, 0.03]:
        hls = []
        for seed in SEEDS:
            random.seed(seed + 100000)
            n_alive_p = N
            hl_p = None
            for g in range(200):
                for _ in range(n_alive_p):
                    if random.random() < lam:
                        n_alive_p -= 1
                if hl_p is None and n_alive_p <= N / 2:
                    hl_p = g
                if n_alive_p <= 0:
                    break
            hls.append(hl_p)
        print(f"    λ={lam:.2f}: 纯退出半衰期={statistics.mean(hls):.1f}"
              f"(含复制版 "
              f"{statistics.mean([r['ro']['half_life'] for r in nofill[lam]]):.1f}"
              f")------复制不改变衰减速度")
    print()

    bar("Part 5  预注册假设检验")
    print()
    h1a_monotone = True
    for lam in [0.01, 0.02, 0.03, 0.10]:
        for r in nofill[lam]:
            dec = any(r['scale'][g + 1] > r['scale'][g]
                      for g in range(len(r['scale']) - 1))
            if dec:
                h1a_monotone = False
    print(f"  H1a(不补档:任何非零 λ 规模单调衰减,无稳定区间):")
    print(f"    20 条轨迹(4λ × 5 seed)逐代单调:"
          f"{'全部单调' if h1a_monotone else '存在回升'} → "
          f"{'支持' if h1a_monotone else '不支持'}")
    print(f"    半衰期随 λ 缩短(0.01→{statistics.mean([r['ro']['half_life'] for r in nofill[0.01]]):.0f}"
          f" →0.03→{statistics.mean([r['ro']['half_life'] for r in nofill[0.03]]):.0f} 代)")
    print()
    pf_plat = [statistics.mean([r['ro']['platform'] for r in filled[lam]])
               for lam in [0.01, 0.02, 0.03]]
    h1b_platform = all(p > 30 for p in pf_plat)
    h1b_decline = pf_plat[0] > pf_plat[1] > pf_plat[2]
    print(f"  H1b(补位档:非零平衡平台,平台随 λ 下降):")
    print(f"    平台 {pf_plat[0]:.0f} / {pf_plat[1]:.0f} / {pf_plat[2]:.0f}"
          f"(λ=0.01/0.02/0.03)")
    print(f"    → {'支持' if h1b_platform and h1b_decline else '不支持'}"
          f"(非零平台{'成立' if h1b_platform else '不成立'},"
          f"随 λ {'下降' if h1b_decline else '未降'})")
    print()
    r003 = statistics.mean([r['ro']['r_live'] for r in nofill[0.03]])
    end003 = statistics.mean([r['end_alive'] for r in nofill[0.03]])
    print(f"  断的形式(主结论):断的是规模不是基因流------")
    print(f"    λ=0.03 末态存活均值={end003:.0f} 人,"
          f"亲子 θ 相关仍 {r003:.3f}")
    print(f"    基因流只在规模归零处才断(λ=0.10 归零档)。")
    print()

    bar("Part 6  诚实边界")
    print("  Limitations:")
    print("    1. 退出率为常数(不随规模/环境反馈),未扫时变退出。")
    print("    2. 补位规则仅一档(延迟 5 代、邻域 q_eff 最大填充)。")
    print("    3. 亲子相关为全 run 合并口径(剔自体、存活≥30 代),"
          "非逐代。")
    print("    4. 单任务、5 seed、教学级。")
    print()
    print("  Future Work:")
    print("    1. 补位规则的其他形式(退出者遗产、变异步长补位)。")
    print("    2. 退出率与规模的反馈耦合(密度依赖退出)。")
    print()

    bar("Part 7  总结")
    print(f"  λ=0 逐位复现 204a 主实验:{'√' if verify_ok else '×'}")
    print(f"  不补档:全档单调衰减(H1a 支持),λ=0.10 归零;")
    print(f"  补位档:平台 {pf_plat[0]:.0f}/{pf_plat[1]:.0f}/{pf_plat[2]:.0f}"
          f"(H1b 支持)。")
    print(f"  断的是规模不是基因流:缩到 {end003:.0f} 人相关仍 {r003:.3f}。")
    print()
    print("  与 204 的衔接:204 答遗传通道(θ 怎么传),本篇开生死+存续")
    print("  通道------存续不取决于退出多弱,取决于有没有补位。")
    print()
    print("  互动钩子:退出压力下火种没断(补位平台成立),")
    print("  那分化被逼出来了吗?------下一篇(205-2)的问题。")
    print()
相关推荐
天赐范式7 天前
天赐范式第198天:让策略开始起源 —— 突变翻转类型下的自发起源
数字生命·天赐范式·动态运行时·奖励外置·自发起源·突变翻转·演化动力学
天赐范式14 天前
天赐范式第192天:让接口开始能调 —— v0.2.0 落地与三仓库最小复现
数字生命·天赐范式·动态运行时·能读能调·三仓库复现·反向方案·可调用包
天赐范式16 天前
天赐范式第190天·第二篇:让债务开始偿还 —— 不立新债,还旧债:v→0 极限的解析闭式,与一根被捡起来的绳子
数字生命·复杂系统·天赐范式·动态运行时·解析闭式·网格钉点·暖启动
天赐范式17 天前
天赐范式第188天:让环境开始轮回——周期峰的低通滤波响应
低通滤波·数字生命·复杂系统·天赐范式·动态运行时·周期驱动·频率响应
天赐范式18 天前
天赐范式第187天:让峰开始移动——lag load定量
数字生命·复杂系统·天赐范式·动态运行时·lag load·移动峰·测量滞后
天赐范式19 天前
天赐范式第187天:让变异开始定价——动态环境下最优变异率
数字生命·复杂系统·天赐范式·动态运行时·最优变异率·bet-hedging·初始条件暂态
天赐范式19 天前
天赐范式第186天:让方差开始自洽——Bulmer效应的闭式验证
python·数字生命·天赐范式·动态运行时·遗传方差·bulmer效应
天赐范式20 天前
天赐范式第185天:让漂变开始定量——扫N看选择主导边界
python·信噪比·数字生命·天赐范式·动态运行时·种群大小·遗传漂变
天赐范式21 天前
天赐范式第183天:让平衡开始定量——收缩与选择的平衡点位置公式验证
数字生命·天赐范式·动态运行时·育种者方程·非线性归因·多seed验证·平衡位移