天赐范式第198天:让策略开始起源 —— 突变翻转类型下的自发起源

天赐范式第198天·第一篇:让策略开始起源 ------ 突变翻转类型下的自发起源

摘要

全A起步,突变以概率μ翻转类型,三臂×20seed。B自发起源:min首突体优势1.96×、sqrt 7.0×,到均衡中位数7/6代。外置各钉角色最优,内置β_A塌向0.5角色消解。④前件③齐备。

1 回扣:④的前件 2/3,本篇补第三件

197-1《让稀有开始占优》走通频率依赖选择:同一环境多策略可维持(前件①可维持)。

197-2《让奖励开始外置》走通奖励外置:A/B 两型互补共存,25/25 自稳定(前件②可互补)。

但两篇的策略是预设的------初始就放好 A/B 两型各 25,不是从突变起源。④自发分工还欠前件③「可起源」:策略能不能从突变中自发起源?

本篇初始全 A 型,突变以概率 μ 翻转类型(A↔B),看 B 能否自发起源、建立、到均衡。

2 实验设计

复刻 197-2 骨架(禁改既有机制):

  • N = 50;型间确定性复制子更新(T_k = S_k + P,n_A' = round(N·T_A/(T_A+T_B)));型内 K=3 锦标赛 + 变异 σ_v = 0.02,β 截断 0,1
  • 初始全 A 型,β ~ N(0.3, 0.01) 截断,seed = 42 起手式与 197-2 一致
  • 角色最优 β*_A = 0.3 / β*_B = 0.7;q_i = 1/(1+(β_i−β*_type)²)

突变(本篇唯一新机制):refill 产生每个后代时以概率 μ 翻转类型(A↔B),β 在新角色最优附近重采 N(β*_new, 0.01) 截断;不翻转则 β = 父代 + N(0, σ_v)。

三臂(同种子、同初值、同突变流,只变奖励/G):

臂 奖励 生产函数
外置 min fitness = q_i + P/n_type P = min(S_A, S_B)
外置 sqrt fitness = q_i + P/n_type P = √(S_A·S_B)
内置 fitness = q(β, β_env) β_env = 0.5(与 197-2 内置臂连续)

μ ∈ {0.02, 0.05, 0.10},每臂每档 20 seed(seed 列表 range(42, 62),跨臂跨档同一组)。

3 首突体优势(49/1 实态解析)

全 A 起步,首突体出现时 49A/1B,q ≈ 1:

生产函数 P f_B f_A 优势比
min 1.0 2.00 1.0204 1.96×
sqrt 7.0 8.00 1.1429 7.00×

min 给 P = min(49, 1) = 1,稀有型 B 每个体分到 P/1 = 1.0;sqrt 给 P = √(49×1) = 7,每个 B 分到 7.0。弱互补(sqrt)的稀有型优势大得多------首突体入侵更快。

4 μ=0.05 seed=42:三臂起源轨迹

组成 n_A(t):

代 外置 min 外置 sqrt 内置
0 50 50 50
2 44 41 46
4 35 29 49
6 27 26 48
8 27 27 46
10 24 26 49
15 24 24 49
20 23 23 48
50 25 25 47
100 23 26 46

外置 min 约 10 代到 25±1,外置 sqrt 约 6 代------sqrt 首突体优势 7.0× vs min 1.96×,入侵更快,与 §3 解析一致。内置臂 n_A 长期偏高(46--49),B 零星出现但不建立------角色消解(§7 详述)。

β 均值终态:

臂 β_A β_B
外置 min 0.3021 0.6984
外置 sqrt 0.2928 0.7081
内置 0.5034 0.7023

外置两臂 β 各钉角色最优(0.3/0.7),内置 β_A 塌向 0.5------角色消解。

5 μ=0.05 20 seed:起源时间分布

臂 首起源(n_B≥5) 中位数 极值 到均衡(|n_A−25|≤1) 中位数 极值
外置 min 2 1~3 7 5~11
外置 sqrt 2 1~2 6 4~10

两臂首起源都在第 2 代(突变率 μ=0.05,50 个体每代期望 2.5 个翻转,B 达 5 个约需 2 代)。到均衡 sqrt 比 min 快 1 代(6 vs 7),与首突体优势差一致。

6 μ 三档 churn:组成振荡极差

到均衡后组成在 25 附近振荡(μ=0.05 极差约 8,μ=0.10 极差约 10,见下表)------突变在均衡附近空转,churn 是本篇读数不是噪声。

μ 外置 min 极差 外置 sqrt 极差
0.02 5.7 5.8
0.05 8.6 8.0
0.10 10.6 10.5

μ 越大 churn 越大。μ=0.10 时组成极差约 10.6------突变每代期望 5 个翻转,组成在 20--30 间大幅摆动但仍不塌缩。

7 内置臂角色消解(20 seed)

内置单环境 β_env = 0.5,全体 β 塌到唯一环境最优:

μ β_A 终态 β_B 条件均值 n_B=0 seed 数 n_A 标签
0.02 0.4995 0.7077 9/20 49.2
0.05 0.4993 0.7022 1/20 47.8
0.10 0.4990 0.6998 0/20 45.5

β_A 终态 ≈ 0.499------塌向 β_env = 0.5(选择把多数派 β 拉向环境最优)。β_B 条件均值三档恒定 ≈0.70,与 μ 无关:B 的 fits 系统性低于 A(B/A≈0.96),全局锦标赛里短命,存活 B 几乎全是当代新突变体(β 重置值 0.7)。μ 只改变 B 的平衡密度(n_A 标签 49.2→45.5)。角色结构半消解:A 角色被环境吞没,B 角色靠突变注入维持。型标签近中性偏弱劣势,组成由突变-选择平衡决定,无恢复力。

对照外置臂:β_A 钉 0.3、β_B 钉 0.7(§4),两角色都靠选择维持。本篇论点:外置分成让角色结构靠选择维持,内置让 A 角色消解------不是清除,是消解。

8 ④自发分工前件状态

前件 篇目 机制 状态
①可维持 197-1 频率依赖选择 可复现
②可互补 197-2 奖励外置 可复现
③可起源 本篇 突变翻转类型 可复现
④合流 --- 三件同一运行 Future Work

三前件各有可复现 demo,但尚未在同一运行中合流(合流列 Future Work)。「路径」一词仍专指 172-1 能力五步,篇目分组称「篇目分期」。

Limitations & Future Work

Limitations:

  1. 突变得的是类型,互补结构(生产函数 G)仍是预设的。
  2. 外置臂型间确定性更新下 B 一旦出现即建立(无建立失败);内置臂 B 不建立(低密度)。若型间改用抽样,小概率建立失败(197-2 同源),多 seed 已部分覆盖。
  3. 三前件各有 demo,尚未在同一运行中合流。
  4. 教学级,非生产安全。

Future Work:

  1. 三前件在同一运行中合流(199 方向)。
  2. 型间也用抽样更新 + 多 seed + 更大 N,看建立失败率。

Reproducibility

兑现 197-2 Future Work:多 seed + 策略自发起源。

三臂同种子同初值同突变流,只变奖励/G------隔离「奖励结构」为唯一变量。Python 3 标准库无第三方依赖,20 seed 固定列表,直接运行可复现全部结果。

附录:完整代码

python 复制代码
# -*- coding: utf-8 -*-
"""
天赐范式 第198天 第一篇
让策略开始起源 ------ 自发起源

PID: TC-198A-V3.3.36.0
V3.3.36.0 · 2026-10-17

兑现 197-2 Future Work:多 seed + 策略自发起源。
初始全 A 型,突变以概率 μ 翻转类型(A↔B),β 在新角色最优附近重采。
三臂对照:外置 min / 外置 sqrt / 内置 β_env=0.5。
μ 三档 {0.02, 0.05, 0.10} × 20 seed。
"""
import sys
import math
import random
import unicodedata

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

N_POP = 50
N_GENS = 100
TOURNAMENT_K = 3
SIGMA_V = 0.02
BETA_A = 0.3
BETA_B = 0.7
BETA_ENV = 0.5
SEEDS = list(range(42, 62))
MU_LIST = [0.02, 0.05, 0.10]


def bar(s=""):
    print("=" * 74)
    if s:
        print("  " + s)
        print("=" * 74)


def disp_w(s):
    # 终端显示宽度:CJK 终端把 F/W 与 A(歧义) 字符都渲染成 2 列
    return sum(2 if unicodedata.east_asian_width(ch) in ("F", "W", "A") else 1 for ch in s)


def md_table(headers, rows, aligns):
    """打印终端等宽的 markdown 表:列宽按显示宽度自适应,aligns='l'/'r'。"""
    widths = [max(disp_w(str(c)) for c in col) for col in zip(headers, *rows)]

    def fmt(row):
        cells = []
        for cell, w, a in zip(row, widths, aligns):
            cell = str(cell)
            gap = " " * (w - disp_w(cell))
            cells.append(gap + cell if a == "r" else cell + gap)
        return "  | " + " | ".join(cells) + " |"

    print(fmt(headers))
    print("  |" + "|".join("-" * max(3, w + 2) for w in widths) + "|")
    for row in rows:
        print(fmt(row))


def q_i(beta, beta_star):
    return 1.0 / (1.0 + (beta - beta_star) ** 2)


def clamp(x):
    return max(0.0, min(1.0, x))


def init_pop_all_a(seed):
    random.seed(seed)
    betas = [clamp(BETA_A + random.gauss(0, 0.01)) for _ in range(N_POP)]
    types = ['A'] * N_POP
    return betas, types


def run_external(mu, seed, prod_fn):
    betas, types = init_pop_all_a(seed)

    history = []
    for gen in range(N_GENS + 1):
        q_vals = [q_i(betas[i], BETA_A if types[i] == 'A' else BETA_B) for i in range(N_POP)]
        s_a = sum(q_vals[i] for i in range(N_POP) if types[i] == 'A')
        s_b = sum(q_vals[i] for i in range(N_POP) if types[i] == 'B')
        p = prod_fn(s_a, s_b)
        n_a = sum(1 for t in types if t == 'A')
        n_b = N_POP - n_a

        mean_a = sum(betas[i] for i in range(N_POP) if types[i] == 'A') / n_a if n_a > 0 else 0
        mean_b = sum(betas[i] for i in range(N_POP) if types[i] == 'B') / n_b if n_b > 0 else 0

        history.append({
            "gen": gen, "n_a": n_a, "mean_a": mean_a, "mean_b": mean_b,
            "s_a": s_a, "s_b": s_b, "p": p,
        })

        if gen == N_GENS:
            break

        t_a = s_a + p
        t_b = s_b + p
        n_a_new = round(N_POP * t_a / (t_a + t_b)) if (t_a + t_b) > 0 else n_a
        n_a_new = max(1, min(N_POP - 1, n_a_new))

        a_indices = [i for i in range(N_POP) if types[i] == 'A']
        b_indices = [i for i in range(N_POP) if types[i] == 'B']

        new_betas = []
        new_types = []

        for _ in range(n_a_new):
            pool = a_indices if len(a_indices) > 0 else b_indices
            k = min(TOURNAMENT_K, len(pool))
            candidates = random.sample(pool, k)
            winner = max(candidates, key=lambda idx: q_vals[idx])
            if random.random() < mu:
                new_types.append('B')
                new_betas.append(clamp(BETA_B + random.gauss(0, 0.01)))
            else:
                new_types.append('A')
                new_betas.append(clamp(betas[winner] + random.gauss(0, SIGMA_V)))

        for _ in range(N_POP - n_a_new):
            pool = b_indices if len(b_indices) > 0 else a_indices
            k = min(TOURNAMENT_K, len(pool))
            candidates = random.sample(pool, k)
            winner = max(candidates, key=lambda idx: q_vals[idx])
            if random.random() < mu:
                new_types.append('A')
                new_betas.append(clamp(BETA_A + random.gauss(0, 0.01)))
            else:
                new_types.append('B')
                new_betas.append(clamp(betas[winner] + random.gauss(0, SIGMA_V)))

        betas = new_betas
        types = new_types

    return history


def run_builtin(mu, seed):
    betas, types = init_pop_all_a(seed)

    history = []
    for gen in range(N_GENS + 1):
        fits = [1.0 / (1.0 + (b - BETA_ENV) ** 2) for b in betas]
        n_a = sum(1 for t in types if t == 'A')
        n_b = N_POP - n_a

        mean_a = sum(betas[i] for i in range(N_POP) if types[i] == 'A') / n_a if n_a > 0 else 0
        mean_b = sum(betas[i] for i in range(N_POP) if types[i] == 'B') / n_b if n_b > 0 else 0

        q_vals = [q_i(betas[i], BETA_A if types[i] == 'A' else BETA_B) for i in range(N_POP)]
        s_a = sum(q_vals[i] for i in range(N_POP) if types[i] == 'A')
        s_b = sum(q_vals[i] for i in range(N_POP) if types[i] == 'B')

        history.append({
            "gen": gen, "n_a": n_a, "mean_a": mean_a, "mean_b": mean_b,
            "s_a": s_a, "s_b": s_b, "p": 0.0,
        })

        if gen == N_GENS:
            break

        new_betas = []
        new_types = []
        for _ in range(N_POP):
            candidates = random.sample(range(N_POP), TOURNAMENT_K)
            winner = max(candidates, key=lambda idx: fits[idx])
            if random.random() < mu:
                new_type = 'B' if types[winner] == 'A' else 'A'
                new_beta_star = BETA_B if new_type == 'B' else BETA_A
                new_types.append(new_type)
                new_betas.append(clamp(new_beta_star + random.gauss(0, 0.01)))
            else:
                new_types.append(types[winner])
                new_betas.append(clamp(betas[winner] + random.gauss(0, SIGMA_V)))

        betas = new_betas
        types = new_types

    return history


def median(vals):
    s = sorted(vals)
    n = len(s)
    return s[n // 2] if n % 2 == 1 else (s[n // 2 - 1] + s[n // 2]) / 2


def main():
    bar("天赐范式 第198天 第一篇  让策略开始起源")

    # ===== Part 0  回扣 =====
    bar("Part 0  回扣:④的前件 2/3,本篇补第三件")
    print("  197-1 频率依赖:同环境多策略可维持(前件①)。")
    print("  197-2 奖励外置:互补共存可复现(前件②)。")
    print("  但策略是预设的,不是从突变起源------前件③「可起源」还欠。")
    print("  本篇:初始全 A 型,突变翻转类型,看 B 能否自发起源。")
    print()

    # ===== Part 1  首突体优势(解析) =====
    bar("Part 1  首突体优势(49/1 实态解析)")
    print("  全 A 起步,首突体出现时 49A/1B,q≈1:")
    s_a_49 = 49.0
    s_b_1 = 1.0
    p_min = min(s_a_49, s_b_1)
    p_sqrt = math.sqrt(s_a_49 * s_b_1)
    fit_a_min = 1.0 + p_min / 49
    fit_b_min = 1.0 + p_min / 1
    fit_a_sqrt = 1.0 + p_sqrt / 49
    fit_b_sqrt = 1.0 + p_sqrt / 1
    ratio_min = fit_b_min / fit_a_min
    ratio_sqrt = fit_b_sqrt / fit_a_sqrt
    print(f"  min:  P={p_min:.1f}, f_B/f_A = {fit_b_min:.2f}/{fit_a_min:.4f} ≈ {ratio_min:.2f}")
    print(f"  sqrt: P={p_sqrt:.1f}, f_B/f_A = {fit_b_sqrt:.2f}/{fit_a_sqrt:.4f} ≈ {ratio_sqrt:.2f}")
    print(f"  → sqrt 首突体优势 {ratio_sqrt:.1f}× vs min {ratio_min:.2f}×,入侵更快")
    print()

    # ===== Part 2  μ=0.05 seed=42 轨迹 =====
    bar("Part 2  μ=0.05 seed=42:三臂起源轨迹")
    mu0 = 0.05
    hist_min = run_external(mu0, 42, lambda sa, sb: min(sa, sb))
    hist_sqrt = run_external(mu0, 42, lambda sa, sb: math.sqrt(sa * sb))
    hist_builtin = run_builtin(mu0, 42)

    print("  组成 n_A(t):")
    rows = [[str(gen),
             str(hist_min[gen]['n_a']),
             str(hist_sqrt[gen]['n_a']),
             str(hist_builtin[gen]['n_a'])] for gen in (0, 2, 4, 6, 8, 10, 15, 20, 50, 100)]
    md_table(["代", "外置min", "外置sqrt", "内置"], rows, "rrrr")
    print()

    print("  β 均值终态:")
    print(f"    外置min:  β_A={hist_min[-1]['mean_a']:.4f}, β_B={hist_min[-1]['mean_b']:.4f}")
    print(f"    外置sqrt: β_A={hist_sqrt[-1]['mean_a']:.4f}, β_B={hist_sqrt[-1]['mean_b']:.4f}")
    print(f"    内置:     β_A={hist_builtin[-1]['mean_a']:.4f}, β_B={hist_builtin[-1]['mean_b']:.4f}")
    print("  → 外置各钉角色最优(0.3/0.7),内置 β_A 塌向 0.5(角色消解),β_B 由突变重置维持")
    print()

    # ===== Part 3  20 seed 起源时间分布 =====
    bar("Part 3  μ=0.05 20 seed:起源时间分布")
    mu0 = 0.05
    for arm_name, run_fn in [
        ("外置min",  lambda s: run_external(mu0, s, lambda sa, sb: min(sa, sb))),
        ("外置sqrt", lambda s: run_external(mu0, s, lambda sa, sb: math.sqrt(sa * sb))),
    ]:
        first_orig = []
        to_eq = []
        for sd in SEEDS:
            h = run_fn(sd)
            fo = next((h[g]['gen'] for g in range(len(h)) if N_POP - h[g]['n_a'] >= 5), 999)
            te = next((h[g]['gen'] for g in range(len(h)) if abs(h[g]['n_a'] - 25) <= 1), 999)
            first_orig.append(fo)
            to_eq.append(te)
        print(f"  {arm_name}:")
        print(f"    首起源(n_B≥5): 中位数={median(first_orig):.0f}  极值={min(first_orig)}~{max(first_orig)}")
        print(f"    到均衡(|n_A-25|≤1): 中位数={median(to_eq):.0f}  极值={min(to_eq)}~{max(to_eq)}")
    print()

    # ===== Part 4  μ 三档 churn 对比 =====
    bar("Part 4  μ 三档 churn:组成振荡极差(20 seed 均值)")
    rows = []
    for mu in MU_LIST:
        amps_min = []
        amps_sqrt = []
        for sd in SEEDS:
            h_min = run_external(mu, sd, lambda sa, sb: min(sa, sb))
            h_sqrt = run_external(mu, sd, lambda sa, sb: math.sqrt(sa * sb))
            tail_min = [h['n_a'] for h in h_min[50:]]
            tail_sqrt = [h['n_a'] for h in h_sqrt[50:]]
            amps_min.append(max(tail_min) - min(tail_min))
            amps_sqrt.append(max(tail_sqrt) - min(tail_sqrt))
        avg_min = sum(amps_min) / len(amps_min)
        avg_sqrt = sum(amps_sqrt) / len(amps_sqrt)
        rows.append([f"{mu:.2f}", f"{avg_min:.1f}", f"{avg_sqrt:.1f}"])
    md_table(["μ", "外置min 极差", "外置sqrt 极差"], rows, "rrr")
    print("  → μ 越大 churn 越大(突变在均衡附近空转)")
    print()

    # ===== Part 5  内置臂角色消解 =====
    bar("Part 5  内置臂角色消解(20 seed)")
    for mu in MU_LIST:
        mean_a_end = []
        cond_mean_b = []
        n_a_end = []
        n_b_zero = 0
        for sd in SEEDS:
            h = run_builtin(mu, sd)
            mean_a_end.append(h[-1]['mean_a'])
            n_b = N_POP - h[-1]['n_a']
            if n_b == 0:
                n_b_zero += 1
            else:
                cond_mean_b.append(h[-1]['mean_b'])
            n_a_end.append(h[-1]['n_a'])
        avg_cond_b = sum(cond_mean_b) / len(cond_mean_b) if cond_mean_b else 0
        print(f"  μ={mu:.2f}: β_A终态={sum(mean_a_end)/len(mean_a_end):.4f}, "
              f"β_B条件均值={avg_cond_b:.4f}, "
              f"n_B=0 seed数={n_b_zero}/20, "
              f"n_A标签={sum(n_a_end)/len(n_a_end):.1f}")
    print("  → 内置单环境:β_A 塌向 0.5(角色消解),β_B 条件均值恒 ≈0.70(B 弱劣势 fits B/A≈0.96,短命,存活 B 几乎全是当代新突变体);型标签近中性偏弱劣势")
    print()

    # ===== Part 6  三前件状态表 =====
    bar("Part 6  ④自发分工前件状态")
    rows = [
        ["①可维持", "197-1", "频率依赖选择", "可复现"],
        ["②可互补", "197-2", "奖励外置", "可复现"],
        ["③可起源", "本篇", "突变翻转类型", "可复现"],
        ["④合流", "---", "三件同一运行", "Future Work"],
    ]
    md_table(["前件", "篇目", "机制", "状态"], rows, "llll")
    print()

    # ===== Limitations & Future Work =====
    bar("Limitations & Future Work")
    print("  Limitations:")
    print("    1. 突变得的是类型,互补结构(生产函数 G)仍是预设的。")
    print("    2. 外置臂型间确定性更新下 B 一旦出现即建立(无建立失败);")
    print("       内置臂 B 不建立(低密度)。若型间改用抽样,小概率建立失败(197-2 同源)。")
    print("    3. 「分工」二字不出口:三前件各有 demo,尚未在同一运行中合流。")
    print("    4. 教学级,非生产安全。")
    print()
    print("  Future Work:")
    print("    1. 三前件在同一运行中合流(199 方向)。")
    print("    2. 型间也用抽样更新 + 多 seed + 更大 N,看建立失败率。")
    print()

    # ===== Reproducibility =====
    bar("Reproducibility")
    print("  兑现 197-2 Future Work:多 seed + 策略自发起源。")
    print("  三臂同种子同初值同突变流,只变奖励/G------隔离「奖励结构」为唯一变量。")
    print("  Python 3 标准库无第三方依赖,20 seed 固定列表,直接运行可复现全部结果。")
    print()


if __name__ == "__main__":
    main()

系列在建:天赐范式第198天,两篇制第一篇。接 197-2 Future Work 钩子------策略自发起源。三前件①可维持②可互补③可起源各有 demo,但尚未合流。留第二篇《让分成开始可验》。


相关推荐
天赐范式7 天前
天赐范式第192天:让接口开始能调 —— v0.2.0 落地与三仓库最小复现
数字生命·天赐范式·动态运行时·能读能调·三仓库复现·反向方案·可调用包
天赐范式9 天前
天赐范式第190天·第二篇:让债务开始偿还 —— 不立新债,还旧债:v→0 极限的解析闭式,与一根被捡起来的绳子
数字生命·复杂系统·天赐范式·动态运行时·解析闭式·网格钉点·暖启动
天赐范式11 天前
天赐范式第188天:让环境开始轮回——周期峰的低通滤波响应
低通滤波·数字生命·复杂系统·天赐范式·动态运行时·周期驱动·频率响应
天赐范式11 天前
天赐范式第187天:让峰开始移动——lag load定量
数字生命·复杂系统·天赐范式·动态运行时·lag load·移动峰·测量滞后
天赐范式12 天前
天赐范式第187天:让变异开始定价——动态环境下最优变异率
数字生命·复杂系统·天赐范式·动态运行时·最优变异率·bet-hedging·初始条件暂态
天赐范式13 天前
天赐范式第186天:让方差开始自洽——Bulmer效应的闭式验证
python·数字生命·天赐范式·动态运行时·遗传方差·bulmer效应
天赐范式14 天前
天赐范式第185天:让漂变开始定量——扫N看选择主导边界
python·信噪比·数字生命·天赐范式·动态运行时·种群大小·遗传漂变
天赐范式15 天前
天赐范式第183天:让平衡开始定量——收缩与选择的平衡点位置公式验证
数字生命·天赐范式·动态运行时·育种者方程·非线性归因·多seed验证·平衡位移
天赐范式16 天前
天赐范式第183天:让选择开始生效——育种者方程与选择生效条件
python·数字生命·天赐范式·动态运行时·育种者方程·选择压力·遗传力