ASSEMBLY-WP-0016-T02: endogenous foreign shares and split calibration hook (domestic segment with own counters); share-shifting levers effective but modest; CrossBorder § 8
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 14913@bnt-lap001 Assistant-Session: add7ef69-b901-4756-9a67-34cf1125d96f
This commit is contained in:
parent
8bb8c8fd4b
commit
824187da3b
6 changed files with 159 additions and 7 deletions
|
|
@ -325,3 +325,50 @@ Caveats: export demand scales only with Argentina's own K_Ω (no price or
|
|||
competition effects, no partner demand). Adding it at t 0 shifts the
|
||||
calibrated rest point slightly; this is negligible at 1.5 % and noticeable at
|
||||
10 %.
|
||||
|
||||
## 8. Endogenous foreign shares and the split calibration hook (WP-0016-T02)
|
||||
|
||||
**Endogenous shares.** `world.make(..., endo=...)` lets each foreign share f
|
||||
relax (speed λ) toward
|
||||
|
||||
f* = f0 · (1 + γ · Δ frontier lead) · (1 − δ · Δ(X · N))
|
||||
|
||||
that is, toward more foreign use when the home's frontier lead grows and less
|
||||
when domestic alternatives (commons X, diversity N) grow. At the start f* = f0,
|
||||
so the measured shares are a rest point. Priors: λ = 0.1 per year, γ = δ = 1.
|
||||
|
||||
**Split calibration hook.** The first hook set the domestic and operator parts
|
||||
of each channel to the same measured level. That made every share-shifting
|
||||
lever (portability, endogenous shares) ineffective by construction: moving
|
||||
users between equally concentrated segments changes nothing. With
|
||||
`d0 = {channel: domestic-segment concentration}`, the domestic part starts at
|
||||
d0 with its own counter coefficients, so it is at rest there. The operator
|
||||
part is solved so that the total keeps its measured level. EU priors: search
|
||||
0.5 (Ecosia, Qwant: 1–2 % share, one or two firms), cloud 0.3 (EU providers,
|
||||
about 15 %, several firms), lineages 0.5 (Mistral, open-weight models).
|
||||
Mobile OS has no EU segment.
|
||||
|
||||
| EU lever (coupled, t 100) | EU P | Δ vs. current |
|
||||
|---|---|---|
|
||||
| endogenous shares, current levers | 0.818 | −0.003 |
|
||||
| commons push (IX × 3), fixed shares | 0.807 | −0.014 |
|
||||
| split hook + commons push + endogenous shares | **0.793** | −0.027 |
|
||||
| split hook + lineage portability 0.5 | **0.802** | −0.019 |
|
||||
| (for comparison) strong access rule on Pd, Pmem | 0.683 | −0.138 |
|
||||
| (for comparison) US moves to J1 levers | 0.635 | −0.186 |
|
||||
|
||||
Readings:
|
||||
|
||||
1. **Share-shifting levers work once the domestic segment is measured
|
||||
separately.** Portability and commons-driven substitution lower EU
|
||||
domination, but by a fifth to a tenth of what access rules or the home's
|
||||
levers achieve.
|
||||
2. **The measured foreign shares are close to rest under current levers**
|
||||
(−0.003 over 100 years): nothing in today's dynamics shifts users toward
|
||||
domestic alternatives by itself.
|
||||
3. **The domestic-segment levels d0 are priors.** They need indicator CB3
|
||||
(open-weight downloads by developer) and the domestic-provider shares in
|
||||
CB1 and CB4.
|
||||
|
||||
Calibration note: runs without `d0` keep the original hook, so all earlier
|
||||
results are unchanged.
|
||||
|
|
|
|||
|
|
@ -36,6 +36,12 @@ STRONG = {"Pd": 0.15, "Pmem": 0.15} # counterfactual strong market-access
|
|||
DMA_EXCESS = -0.017
|
||||
DMA_EXCESS_RANGE = (-0.034, 0.0)
|
||||
FLOOR = 0.70
|
||||
ENDO = {"lam": 0.1, "gamma": 1.0, "delta": 1.0}
|
||||
# Domestic-segment concentration (priors, WP-0016-T02): EU-own search engines (Ecosia, Qwant; 1-2 %
|
||||
# share) dominated by one or two firms; EU cloud providers (about 15 %) spread over several; domestic
|
||||
# and open-weight lineages (Mistral, open models). No EU mobile OS, so Pmem has no domestic segment.
|
||||
D0_EU = {"Pd": 0.5, "Pi": 0.3, "Plin": 0.5}
|
||||
IX_EU = 0.02 # gadm02 default commons investment; the push triples it
|
||||
|
||||
|
||||
def base() -> dict:
|
||||
|
|
@ -46,7 +52,8 @@ def base() -> dict:
|
|||
|
||||
|
||||
def world(cal: dict, coupled: bool = True, us_lev: dict | None = None, eu_access: dict | None = None,
|
||||
eu_lev: dict | None = None, eu_port: float = 0.0) -> dict:
|
||||
eu_lev: dict | None = None, eu_port: float = 0.0, eu_endo: dict | None = None,
|
||||
eu_d0: dict | None = None) -> dict:
|
||||
us_p, us_s = cal["US"]
|
||||
eu_p, eu_s = cal["EU"]
|
||||
ar_p, ar_s = cal["AR"]
|
||||
|
|
@ -54,7 +61,8 @@ def world(cal: dict, coupled: bool = True, us_lev: dict | None = None, eu_access
|
|||
"US": w.make({**us_p, **(us_lev or {})}, us_s),
|
||||
"EU": w.make({**eu_p, **(eu_lev or {})}, eu_s, home="US" if coupled else None,
|
||||
f=F["EU"] if coupled else None, access=eu_access, kappa=KAPPA if coupled else 0.0,
|
||||
port=eu_port if coupled else 0.0),
|
||||
port=eu_port if coupled else 0.0, endo=eu_endo if coupled else None,
|
||||
d0=eu_d0 if coupled else None),
|
||||
"AR": w.make(ar_p, ar_s, home="US" if coupled else None, f=F["AR"] if coupled else None,
|
||||
kappa=KAPPA if coupled else 0.0),
|
||||
}
|
||||
|
|
@ -100,6 +108,15 @@ def cases(a_fit: float) -> list:
|
|||
("coupled, EU cloud switching (Data Act) a 0.15 on Pi", dict(eu_access={"Pi": 0.15})),
|
||||
("coupled, EU all host rules (Pd, Pmem, Plin, Pi 0.15; portability 0.5)",
|
||||
dict(eu_access={"Pd": 0.15, "Pmem": 0.15, "Plin": 0.15, "Pi": 0.15}, eu_port=0.5)),
|
||||
# WP-0016-T02: endogenous foreign shares (priors: speed 0.1/y, weights 1)
|
||||
("coupled, endogenous foreign shares, current levers", dict(eu_endo=ENDO)),
|
||||
("coupled, EU commons push (IX × 3), fixed shares", dict(eu_lev={"IX": 3 * IX_EU})),
|
||||
("coupled, EU commons push (IX × 3), endogenous shares", dict(eu_lev={"IX": 3 * IX_EU}, eu_endo=ENDO)),
|
||||
("coupled, split hook (domestic segment measured), current levers", dict(eu_d0=D0_EU)),
|
||||
("coupled, split hook, endogenous shares", dict(eu_d0=D0_EU, eu_endo=ENDO)),
|
||||
("coupled, split hook, commons push, endogenous shares",
|
||||
dict(eu_d0=D0_EU, eu_endo=ENDO, eu_lev={"IX": 3 * IX_EU})),
|
||||
("coupled, split hook, lineage portability 0.5", dict(eu_d0=D0_EU, eu_port=0.5)),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,27 @@ at most a ≈ 0.006. Counterfactual strong rule: a 0.15.
|
|||
| coupled, EU all host rules (Pd, Pmem, Plin, Pi 0.15; portability 0.5) | US | 0.68 → 0.79 → 0.82 | 0.88 / 0.63 / 0.90 | 0.36 | 0.253 | 0.36 | 0.32 | broken |
|
||||
| coupled, EU all host rules (Pd, Pmem, Plin, Pi 0.15; portability 0.5) | EU | 0.72 → 0.43 → 0.35 | 0.14 / 0.07 / 0.57 | 0.41 | 0.438 | 0.53 | 0.34 | ok |
|
||||
| coupled, EU all host rules (Pd, Pmem, Plin, Pi 0.15; portability 0.5) | AR | 0.75 → 0.81 → 0.83 | 0.88 / 0.63 / 0.90 | 0.32 | 0.237 | 0.34 | 0.26 | broken |
|
||||
| coupled, endogenous foreign shares, current levers | US | 0.68 → 0.79 → 0.82 | 0.88 / 0.63 / 0.90 | 0.36 | 0.253 | 0.36 | 0.32 | broken |
|
||||
| coupled, endogenous foreign shares, current levers | EU | 0.72 → 0.80 → 0.82 | 0.88 / 0.63 / 0.90 | 0.30 | 0.282 | 0.41 | 0.42 | broken |
|
||||
| coupled, endogenous foreign shares, current levers | AR | 0.75 → 0.81 → 0.83 | 0.88 / 0.63 / 0.90 | 0.32 | 0.241 | 0.35 | 0.27 | broken |
|
||||
| coupled, EU commons push (IX × 3), fixed shares | US | 0.68 → 0.79 → 0.82 | 0.88 / 0.63 / 0.90 | 0.36 | 0.253 | 0.36 | 0.32 | broken |
|
||||
| coupled, EU commons push (IX × 3), fixed shares | EU | 0.72 → 0.79 → 0.81 | 0.88 / 0.63 / 0.91 | 0.33 | 0.328 | 0.42 | 0.40 | broken |
|
||||
| coupled, EU commons push (IX × 3), fixed shares | AR | 0.75 → 0.81 → 0.83 | 0.88 / 0.63 / 0.90 | 0.32 | 0.241 | 0.35 | 0.27 | broken |
|
||||
| coupled, EU commons push (IX × 3), endogenous shares | US | 0.68 → 0.79 → 0.82 | 0.88 / 0.63 / 0.90 | 0.36 | 0.253 | 0.36 | 0.32 | broken |
|
||||
| coupled, EU commons push (IX × 3), endogenous shares | EU | 0.72 → 0.79 → 0.81 | 0.88 / 0.63 / 0.90 | 0.33 | 0.327 | 0.42 | 0.40 | broken |
|
||||
| coupled, EU commons push (IX × 3), endogenous shares | AR | 0.75 → 0.81 → 0.83 | 0.88 / 0.63 / 0.90 | 0.32 | 0.241 | 0.35 | 0.27 | broken |
|
||||
| coupled, split hook (domestic segment measured), current levers | US | 0.68 → 0.79 → 0.82 | 0.88 / 0.63 / 0.90 | 0.36 | 0.253 | 0.36 | 0.32 | broken |
|
||||
| coupled, split hook (domestic segment measured), current levers | EU | 0.72 → 0.79 → 0.82 | 0.88 / 0.63 / 0.90 | 0.30 | 0.283 | 0.41 | 0.42 | broken |
|
||||
| coupled, split hook (domestic segment measured), current levers | AR | 0.75 → 0.81 → 0.83 | 0.88 / 0.63 / 0.90 | 0.32 | 0.241 | 0.35 | 0.27 | broken |
|
||||
| coupled, split hook, endogenous shares | US | 0.68 → 0.79 → 0.82 | 0.88 / 0.63 / 0.90 | 0.36 | 0.253 | 0.36 | 0.32 | broken |
|
||||
| coupled, split hook, endogenous shares | EU | 0.72 → 0.79 → 0.82 | 0.88 / 0.63 / 0.90 | 0.30 | 0.282 | 0.41 | 0.42 | broken |
|
||||
| coupled, split hook, endogenous shares | AR | 0.75 → 0.81 → 0.83 | 0.88 / 0.63 / 0.90 | 0.32 | 0.241 | 0.35 | 0.27 | broken |
|
||||
| coupled, split hook, commons push, endogenous shares | US | 0.68 → 0.79 → 0.82 | 0.88 / 0.63 / 0.90 | 0.36 | 0.253 | 0.36 | 0.32 | broken |
|
||||
| coupled, split hook, commons push, endogenous shares | EU | 0.72 → 0.78 → 0.79 | 0.88 / 0.63 / 0.90 | 0.33 | 0.332 | 0.42 | 0.40 | broken |
|
||||
| coupled, split hook, commons push, endogenous shares | AR | 0.75 → 0.81 → 0.83 | 0.88 / 0.63 / 0.90 | 0.32 | 0.241 | 0.35 | 0.27 | broken |
|
||||
| coupled, split hook, lineage portability 0.5 | US | 0.68 → 0.79 → 0.82 | 0.88 / 0.63 / 0.90 | 0.36 | 0.253 | 0.36 | 0.32 | broken |
|
||||
| coupled, split hook, lineage portability 0.5 | EU | 0.68 → 0.77 → 0.80 | 0.88 / 0.63 / 0.86 | 0.31 | 0.293 | 0.41 | 0.41 | broken |
|
||||
| coupled, split hook, lineage portability 0.5 | AR | 0.75 → 0.81 → 0.83 | 0.88 / 0.63 / 0.90 | 0.32 | 0.241 | 0.35 | 0.27 | broken |
|
||||
|
||||
## EU domination at t 100: effect of each lever (coupled model)
|
||||
|
||||
|
|
@ -70,3 +91,10 @@ at most a ≈ 0.006. Counterfactual strong rule: a 0.15.
|
|||
| coupled, EU lineage access 0.15 + portability 0.5 | 0.757 | -0.064 |
|
||||
| coupled, EU cloud switching (Data Act) a 0.15 on Pi | 0.766 | -0.055 |
|
||||
| coupled, EU all host rules (Pd, Pmem, Plin, Pi 0.15; portability 0.5) | 0.350 | -0.471 |
|
||||
| coupled, endogenous foreign shares, current levers | 0.818 | -0.003 |
|
||||
| coupled, EU commons push (IX × 3), fixed shares | 0.807 | -0.014 |
|
||||
| coupled, EU commons push (IX × 3), endogenous shares | 0.807 | -0.014 |
|
||||
| coupled, split hook (domestic segment measured), current levers | 0.817 | -0.004 |
|
||||
| coupled, split hook, endogenous shares | 0.818 | -0.003 |
|
||||
| coupled, split hook, commons push, endogenous shares | 0.793 | -0.027 |
|
||||
| coupled, split hook, lineage portability 0.5 | 0.802 | -0.019 |
|
||||
|
|
|
|||
|
|
@ -74,6 +74,24 @@ class TestWorld(unittest.TestCase):
|
|||
self.assertLess(port["host"][-1]["Plin"], base["host"][-1]["Plin"])
|
||||
self.assertAlmostEqual(port["home"][-1]["Plin"], base["home"][-1]["Plin"], places=12)
|
||||
|
||||
def test_endogenous_shares_rest_at_start_and_fall_with_domestic_alternatives(self):
|
||||
f = {c: 0.8 for c in w.COUPLED}
|
||||
endo = {"lam": 0.5, "gamma": 0.0, "delta": 2.0}
|
||||
still = w.simulate_world({"home": w.make(), "host": w.make(home="home", f=f, endo=endo)}, T=1, record_every=1)
|
||||
self.assertAlmostEqual(still["host"][1]["f_Pd"], 0.8, places=3) # near rest at start
|
||||
push = w.simulate_world({"home": w.make(), "host": w.make({"IX": 0.3}, home="home", f=f, endo=endo)}, T=T)
|
||||
fixed = w.simulate_world({"home": w.make(), "host": w.make({"IX": 0.3}, home="home", f=f)}, T=T)
|
||||
self.assertLess(push["host"][-1]["f_Pd"], 0.8)
|
||||
self.assertEqual(fixed["host"][-1]["f_Pd"], 0.8)
|
||||
|
||||
def test_split_hook_keeps_measured_level(self):
|
||||
f = {c: 0.8 for c in w.COUPLED}
|
||||
j = w.make(home="home", f=f, d0={"Pd": 0.3})
|
||||
self.assertAlmostEqual(w.mixed(j)["Pd"], w.make(home="home", f=f)["s"]["Pd"], places=12)
|
||||
self.assertEqual(j["s"]["Pd"], 0.3)
|
||||
d, _a, _sm = w.step_derivatives({"home": w.make(), "host": j})["host"]
|
||||
self.assertLess(abs(d["Pd"]), 1e-3) # the domestic segment starts at rest
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -34,20 +34,39 @@ OP = {c: "O" + c for c in COUPLED} # operator-part stocks
|
|||
|
||||
|
||||
def make(params: dict | None = None, init: dict | None = None, home: str | None = None,
|
||||
f: dict | None = None, access: dict | None = None, kappa: float = 0.0, port: float = 0.0) -> dict:
|
||||
f: dict | None = None, access: dict | None = None, kappa: float = 0.0, port: float = 0.0,
|
||||
endo: dict | None = None, d0: dict | None = None) -> dict:
|
||||
"""One jurisdiction: gadm04 params/init plus coupling settings.
|
||||
|
||||
home: name of the operators' home jurisdiction (None = no foreign operators);
|
||||
f: foreign share per coupled channel; access: host market-access rule per channel;
|
||||
port: lineage portability rule (WP-0016-T01), 0..1: share of imported-lineage use that users can
|
||||
move to domestic or open lineages; lowers the effective lineage import share f_Plin * (1 - port).
|
||||
endo: endogenous foreign shares (WP-0016-T02), {"lam": speed per year, "gamma": weight of the
|
||||
home's frontier lead, "delta": weight of domestic alternatives X*N}. f relaxes toward
|
||||
f* = f0 * (1 + gamma * (lead - lead0)) * (1 - delta * (alt - alt0)), clipped to [0, 1]; at the
|
||||
start f* = f0, so the measured shares are a rest point.
|
||||
d0: measured concentration of the domestic segment per channel (WP-0016-T02). Without it, the
|
||||
calibration hook sets D(0) = O(0) = P(0), which makes every share-shifting lever ineffective.
|
||||
With it, D(0) = d0 and O(0) is solved so that the mixed channel keeps its measured P(0).
|
||||
"""
|
||||
p = {**g.PARAMS, **(params or {})}
|
||||
s = {**g.INIT, **(init or {})}
|
||||
fs = {c: 0.0 for c in COUPLED} | (f or {})
|
||||
for c in COUPLED:
|
||||
s[OP[c]] = s[c] # calibration hook: D(0) = O(0) = P(0)
|
||||
return {"p": p, "s": s, "home": home, "f": {c: 0.0 for c in COUPLED} | (f or {}),
|
||||
"access": {c: 0.0 for c in COUPLED} | (access or {}), "kappa": kappa, "port": port}
|
||||
if d0 and c in d0 and fs[c] > 0: # split: domestic segment measured, operator part keeps P(0)
|
||||
total = s[c]
|
||||
fc = fs[c] * (1 - port) if c == "Plin" else fs[c]
|
||||
s[c] = d0[c]
|
||||
s[OP[c]] = min(1.0, max(0.0, (total - (1 - fc) * d0[c]) / fc))
|
||||
dom = None
|
||||
if d0: # the domestic segment gets its own channel counters, so that D(0) = d0 is a rest point
|
||||
coef = g.calibrate(p, s)
|
||||
dom = {g3.COEF[c]: coef[g3.COEF[c]] for c in COUPLED if c in d0}
|
||||
return {"p": p, "s": s, "home": home, "f": {c: 0.0 for c in COUPLED} | (f or {}), "dom": dom,
|
||||
"access": {c: 0.0 for c in COUPLED} | (access or {}), "kappa": kappa, "port": port,
|
||||
"endo": endo}
|
||||
|
||||
|
||||
def context(rows: list[dict], params: dict | None = None) -> dict:
|
||||
|
|
@ -94,7 +113,7 @@ def step_derivatives(world: dict, sigma: dict | None = None) -> dict:
|
|||
sm = mixed(j)
|
||||
d, a = g.derivatives(sm, p) # everything the host sees
|
||||
if any(j["f"][c] for c in COUPLED): # domestic channel parts at their own level
|
||||
dd, _a = g.derivatives(j["s"], p)
|
||||
dd, _a = g.derivatives(j["s"], {**p, **(j.get("dom") or {})})
|
||||
for c in COUPLED:
|
||||
d[c] = dd[c]
|
||||
for c in COUPLED: # global spillover acts on domestic parts too
|
||||
|
|
@ -130,6 +149,11 @@ def simulate_world(world: dict, T: float = 100.0, dt: float = 0.1, record_every:
|
|||
_d, a0 = g.derivatives(mixed(j), j["p"])
|
||||
j["p"] = {**j["p"], "sig_pb0": g.perceived_benefit(mixed(j), a0, g.legislated(mixed(j), j["p"]))}
|
||||
keys = g.STOCKS + tuple(OP.values())
|
||||
for name, j in world.items():
|
||||
if j.get("endo") and j["home"] is not None:
|
||||
h = world[j["home"]]["s"]
|
||||
j["f"] = dict(j["f"])
|
||||
j["_ref"] = {"f0": dict(j["f"]), "lead0": h["Ko"] - j["s"]["Ko"], "alt0": j["s"]["X"] * j["s"]["N"]}
|
||||
rows = {k: [] for k in world}
|
||||
for j in world.values():
|
||||
if j.get("path") and record_every and len(j["path"]) < int(round(T / dt)) + 1:
|
||||
|
|
@ -143,9 +167,21 @@ def simulate_world(world: dict, T: float = 100.0, dt: float = 0.1, record_every:
|
|||
**{k: a[k] for k in ("H", "A", "n_ch", "n_w", "n_s", "n_d", "care_need",
|
||||
"Ystar", "B", "V", "Vsel", "Hch", "Hw", "Hs", "Hd")}}
|
||||
row.update({OP[c]: world[name]["s"][OP[c]] for c in COUPLED})
|
||||
row.update({"f_" + c: world[name]["f"][c] for c in COUPLED})
|
||||
row["VFAC"] = g2.vfac(row)
|
||||
rows[name].append(row)
|
||||
if i < steps:
|
||||
for name, j in world.items(): # endogenous foreign shares (previous state)
|
||||
e = j.get("endo")
|
||||
if not e or j["home"] is None:
|
||||
continue
|
||||
ref, h = j["_ref"], world[j["home"]]["s"]
|
||||
lead, alt = h["Ko"] - j["s"]["Ko"], j["s"]["X"] * j["s"]["N"]
|
||||
for c in COUPLED:
|
||||
target = ref["f0"][c] * (1 + e.get("gamma", 1.0) * (lead - ref["lead0"])) \
|
||||
* (1 - e.get("delta", 1.0) * (alt - ref["alt0"]))
|
||||
target = min(1.0, max(0.0, target))
|
||||
j["f"][c] = min(1.0, max(0.0, j["f"][c] + dt * e.get("lam", 0.1) * (target - j["f"][c])))
|
||||
for name, (d, _a, _sm) in der.items():
|
||||
if world[name].get("path"):
|
||||
row = world[name]["path"][min(i + 1, len(world[name]["path"]) - 1)]
|
||||
|
|
|
|||
|
|
@ -42,11 +42,17 @@ after interoperability rules, or the AI Act's GPAI obligations). Re-run
|
|||
|
||||
```task
|
||||
id: ASSEMBLY-WP-0016-T02
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "7a89281d-0cba-5d3f-85c6-6e29cb4f2de8"
|
||||
```
|
||||
|
||||
**Done 2026-10-13:** endogenous foreign shares (`endo`) and the split
|
||||
calibration hook (`d0`, domestic segment with its own counters), tests,
|
||||
`models/CrossBorder.md` § 8. Share-shifting levers work only with the split
|
||||
hook: EU P −0.019 (portability), −0.027 (commons push with endogenous shares).
|
||||
Priors λ, γ, δ and d0 still need data (CB1, CB3, CB4).
|
||||
|
||||
Make f_c^j a state: it falls when domestic alternatives grow (domestic X,
|
||||
N, open lineages) or access rules bind, and rises with the operator's
|
||||
quality lead (frontier gap). Check that today's measured f (§ 2) is a rest
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue