From 7329588731d274003c009cdf08f0a99f10da8bac Mon Sep 17 00:00:00 2001 From: Sergio Gimenez Date: Fri, 26 Jun 2026 08:23:12 +0200 Subject: [PATCH 1/3] ci: enable GitHub Pages deployment --- .github/workflows/deploy-docs.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index e7e570f..b816830 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -9,6 +9,7 @@ permissions: contents: read pages: write id-token: write + administration: write concurrency: group: deploy-docs @@ -31,6 +32,8 @@ jobs: run: zensical build - uses: actions/configure-pages@v5 + with: + enablement: true - uses: actions/upload-pages-artifact@v3 with: From 437a65beb78163923d07d28eb3ddefa2ba7b636f Mon Sep 17 00:00:00 2001 From: Sergio Gimenez Date: Fri, 26 Jun 2026 08:23:12 +0200 Subject: [PATCH 2/3] docs: extend mobility continuity model --- docs/mobility-formal-model.md | 62 +++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/docs/mobility-formal-model.md b/docs/mobility-formal-model.md index 42d79ad..7f67e2d 100644 --- a/docs/mobility-formal-model.md +++ b/docs/mobility-formal-model.md @@ -121,7 +121,7 @@ PSA-region crossing. $$\sigma_{5G}^{\mathrm{PSA\text{-}reloc}} \approx 1500\ \text{bytes}\quad\textit{(approx — to ground in TS 23-502 §4.3.5; SSC 2/3 only)}$$ -### 3.5 SSC-1 anchor path-stretch (the real intra-PLMN cost of pinning) +### 3.4 SSC-1 anchor path-stretch (the real intra-PLMN cost of pinning) With the anchor pinned, 5G traffic hairpins from the current serving edge UPF to the **original** PSA; the optimal egress (what 6G-RUPA achieves by renumbering into the local domain) is the **nearest** PSA. The excess @@ -132,7 +132,7 @@ is exactly *why* SSC-1 pinning is acceptable intra-PLMN. It grows large only whe anchor is far from the user: **roaming** (hairpin to the home country) and edge-compute (motivating SSC 2/3). Measured in-sim: `anchor_dist_5g_sum` / `anchor_dist_opt_sum`. -### 3.4 6G-RUPA — renumbering (all levels, flat) +### 3.5 6G-RUPA — renumbering (all levels, flat) **Mechanism (Grasa et al. 2017 §III; RINA RM lines 1814–1828):** the moving IPCP (UE) obtains a new synonym (address) reflecting its new location and then: 1. **advertises the new address via routing** to its direct neighbours @@ -166,7 +166,7 @@ $$\sigma_{\mathrm{RUPA}}^{\mathrm{renumber}} \approx 200\ \text{bytes (flat acro > domain's aggregate prefix is fixed by topology and already present; the UE merely > adopts an address under it. No core prefix is added or removed at any level. -### 3.5 Roaming +### 3.6 Roaming - **5G Home-Routed** (inter-PLMN): visited UPF anchors back to the home PLMN over N9; per-roaming-session inter-PLMN coordination. $\sigma_{\mathrm{roam}}^{5G,HR} \approx 1180$ B [TS 23-501 §4.2.8.2.3; TS 29-244 §8]. @@ -292,6 +292,62 @@ HR roaming (separate scenarios). Granularity equality holds regardless. --- +## 6b. Service continuity & in-flight packet loss (the "what about packets in flight?" result) + +**Claim: 6G-RUPA renumbering loses no in-flight packets at the core; 5G's +tunnel teardown/re-establish opens a loss window on anchor-affecting handovers.** + +Decompose a handover into a **radio leg** (RRC reconfiguration + random access to the +target cell) and a **core leg** (data-path update): + +- **Radio leg is architecture-agnostic.** 5G and 6G-RUPA use the *same* NG-RAN; the + radio interruption time, radio-link-failure and ping-pong behaviour are identical + and **out of scope** for an addressing-layer comparison. (This is exactly where the + WCNC experiments saw their only loss — an IRATI↔WiFi WPA-supplicant artifact, not + the renumbering — `rina-mobility-wcnc.md` §IV.) +- **Core leg is where the architectures differ.** + - **5G:** an anchor-affecting handover (L2 N2/UL-CL reloc, and SSC 2/3 re-anchor) + **tears down and re-establishes UPF tunnel state** (PFCP Release/Establish). During + that window in-flight packets on the old path are lost or reordered unless masked + by data-forwarding/end-markers; an L3/SSC-2 break-before-make additionally changes + the UE IP. Xn (L1) is the mild case (end-marker forwarding). + - **6G-RUPA:** the flow is **never torn down** — the address is a *synonym*, EFCP is + keyed on port-ids, and the renumber is **make-before-break** (new synonym advertised + + flow-update sent to correspondents *while the old address still forwards*, retired + only after a policy timeout; RM l.1814–1828). So **ΔS_ctx and the flow survive with + zero core-side loss** — *"there are no tunnels to set up and tear down; packet loss + can only occur if there was no physical communication"* (`rina-mobility-wcnc.md` §III; + 0-loss renumbering, Grasa et al. EUCNC 2017). + +$$\text{in-flight core loss} = \begin{cases} >0 \text{ window} & \text{5G anchor-affecting (tunnel re-establish)} \\ 0 & \text{6G-RUPA (make-before-break renumber)} \end{cases}$$ + +We argue this **architecturally** (not via per-packet simulation): the result is a +property of *whether the data path is torn down*, which the σ/state model already +captures (5G re-establishes per-session state; RUPA does not). + +## 6c. Metrics mapping — what the literature measures vs what we isolate + +Standard 5G handover KPIs (surveys): handover interruption time (HOIT), handover +delay/execution time, **packet loss**, throughput, **signaling overhead**, handover +rate (HOR), success/failure rate (HOSR/HOFR), radio-link-failure (RLF), ping-pong. + +| KPI | Layer | In this paper? | +|---|---|---| +| Signaling overhead (σ) | core/CP | **Yes — central** (Xn/N2 vs flat renumber) | +| Forwarding/core-state size & churn | core/UP | **Yes — headline** (our contribution; not a classic mobility KPI) | +| In-flight loss / service continuity | core/UP | **Yes — architectural** (§6b: 5G window vs RUPA 0) | +| Handover rate (HOR) | radio geom. | **Yes** (validated vs Voronoi geometry) | +| Anchor path-stretch | core/UP | **Yes** (§3.4; our addition) | +| HOIT, HOD, RLF, ping-pong, throughput | **radio/RAN** | **No — architecture-agnostic** (same NG-RAN), out of scope | + +The framing for reviewers: classic mobility KPIs are dominated by the **radio** leg, +which is identical for 5G and 6G-RUPA; we deliberately **isolate the core/addressing +leg**, where the architectures actually differ, and contribute two metrics the radio- +centric literature does not track — **core forwarding-state churn** and **anchor +path-stretch**. + +--- + ## 7. Summary table | Handover | 5G σ (B) | 5G ΔS_core | 5G ΔS_ctx (billing) | RUPA σ (B) | RUPA ΔS_core | RUPA ΔS_ctx | From 269761661f4beac8b63f2acc06838da10c0b034b Mon Sep 17 00:00:00 2001 From: Sergio Gimenez Date: Fri, 26 Jun 2026 08:23:12 +0200 Subject: [PATCH 3/3] feat(eval): add national profile variants --- gen_trajectories.jl | 49 ++++++++++++++++++++++++++++++--------------- run_national.jl | 15 +++++++++----- 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/gen_trajectories.jl b/gen_trajectories.jl index ca7e9ce..9d8c9c2 100644 --- a/gen_trajectories.jl +++ b/gen_trajectories.jl @@ -5,7 +5,8 @@ # under the chosen mobility model, and records its path + handover events # (classified L1/L2/L3). Writes viz/data/{trajectories,gnbs,meta}.json. # -# julia --project gen_trajectories.jl [n_agents] [duration_s] [dt_s] [speed_kmh] +# julia --project gen_trajectories.jl [country] [n_agents] [duration_s] [dt_s] [speed_kmh] +# country = spain | usa | usa_asr (default spain; agent count defaults to national) # # No full DES needed: we just iterate step_position + find_serving_gnb per agent, # exactly the per-tick handover check Core.jl does, so levels match the national run. @@ -14,18 +15,34 @@ using DesJulia6gRupa, DesJulia6gRupa.Types import DesJulia6gRupa.Simulation as DSim import DesJulia6gRupa: select_agent_location -const NAG = length(ARGS) >= 1 ? parse(Int, ARGS[1]) : 1500 -const DURATION = length(ARGS) >= 2 ? parse(Float64,ARGS[2]) : 600.0 -const DT = length(ARGS) >= 3 ? parse(Float64,ARGS[3]) : 4.0 -const SPEED = length(ARGS) >= 4 ? parse(Float64,ARGS[4]) : 50.0 +# (data subdir, gNB csv files rel to data/, operator net id, #edge UPFs, population) +# Mirrors run_national.jl PROFILES so trajectory levels match the national run. +const PROFILES = Dict( + "spain" => ("spain", ["opencellid/214.csv"], 7, 52, 49_442_844), + "usa" => ("usa", ["opencellid/310.csv","opencellid/311.csv"], 480, 817, 335_000_000), + "usa_asr" => ("usa", ["asr/310.csv"], 999, 817, 335_000_000), +) +const SCALE = 1000 +const ADOPTION = 0.82 +const NUM_PSA = 5 + +const COUNTRY = lowercase(get(ARGS, 1, "spain")) +haskey(PROFILES, COUNTRY) || error("unknown country $COUNTRY (spain|usa|usa_asr)") +const SUB, FILES, OPID, NEDGE, POP = PROFILES[COUNTRY] +const NAG = length(ARGS) >= 2 ? parse(Int, ARGS[2]) : ceil(Int, POP*ADOPTION/SCALE) +const DURATION = length(ARGS) >= 3 ? parse(Float64,ARGS[3]) : 600.0 +const DT = length(ARGS) >= 4 ? parse(Float64,ARGS[4]) : 4.0 +const SPEED = length(ARGS) >= 5 ? parse(Float64,ARGS[5]) : 50.0 const NSTEPS = floor(Int, DURATION / DT) r5(x) = round(x, digits=5) # coord rounding to shrink the file -println("Building Spain topology (52 edge / 5 PSA, two_tier)...") -paths = filter(isfile, [joinpath(@__DIR__,"data","spain","opencellid","214.csv")]) -topo = DSim.load_and_deploy_network(paths, 7, 52, joinpath(@__DIR__,"data","spain"), - SimConfig(1,2,1000,1,1,1,:two_tier,5,1)) +println("Building $(uppercase(COUNTRY)) topology ($NEDGE edge / $NUM_PSA PSA, two_tier)...") +base = joinpath(@__DIR__, "data", SUB) +paths = filter(isfile, [joinpath(base, f) for f in FILES]) +isempty(paths) && error("no gNB data under $base for $FILES") +topo = DSim.load_and_deploy_network(paths, OPID, NEDGE, base, + SimConfig(1,2,SCALE,1,1,1,:two_tier,NUM_PSA,1)) println("gNBs=$(length(topo.gnb_locations)) edgeUPF=$(length(topo.upf_locations)) PSA=$(length(topo.centralized_upf_locations))") model = RandomWaypoint(SPEED, 0.0, SPEED*DT/3600*2) # max jump ~2 steps of travel @@ -74,15 +91,15 @@ end return nho end -io = open(joinpath(outdir, "trajectories.json"), "w") +io = open(joinpath(outdir, "trajectories-$COUNTRY.json"), "w") print(io, "[") nho = gen_trips(io, topo, model) print(io, "]") close(io) -println("Wrote trajectories.json ($NAG agents, $nho handovers)") +println("Wrote trajectories-$COUNTRY.json ($NAG agents, $nho handovers)") # gNB sites (rounded). 46k points render fine in deck.gl. -open(joinpath(outdir, "gnbs.json"), "w") do f +open(joinpath(outdir, "gnbs-$COUNTRY.json"), "w") do f print(f, "[") for (i, g) in enumerate(topo.gnb_locations) i > 1 && print(f, ",") @@ -90,11 +107,11 @@ open(joinpath(outdir, "gnbs.json"), "w") do f end print(f, "]") end -println("Wrote gnbs.json ($(length(topo.gnb_locations)) sites)") +println("Wrote gnbs-$COUNTRY.json ($(length(topo.gnb_locations)) sites)") -open(joinpath(outdir, "meta.json"), "w") do f - print(f, "{\"country\":\"spain\",\"agents\":$NAG,\"duration\":$(round(Int,DURATION)),", +open(joinpath(outdir, "meta-$COUNTRY.json"), "w") do f + print(f, "{\"country\":\"$COUNTRY\",\"agents\":$NAG,\"duration\":$(round(Int,DURATION)),", "\"dt\":$(round(Int,DT)),\"speed_kmh\":$SPEED,\"nsteps\":$NSTEPS,", - "\"edge_upfs\":52,\"psas\":5,\"handovers\":$nho}") + "\"edge_upfs\":$NEDGE,\"psas\":$NUM_PSA,\"handovers\":$nho}") end println("Done. Output in $outdir") diff --git a/run_national.jl b/run_national.jl index 9fbdd32..be9ee2f 100644 --- a/run_national.jl +++ b/run_national.jl @@ -21,18 +21,23 @@ const SCALE = 1000 const NUM_PSA = 5 # centralized PSAs (config.toml num_centralized_upfs) const ADOPTION = 0.82 # mobile_adoption_rate (config.toml) -# (data subdir, mcc csv files, OpenCellID operator net id, #edge UPFs, population) +# (data subdir, gNB csv files relative to data/, operator net id, #edge UPFs, population) +# usa = OpenCellID Verizon (operator-tagged, sparse — lower density bound) +# usa_asr = FCC ASR all macro structures (operator-agnostic, complete — upper bound; +# net=999 synthetic tag so no operator filter applies). Density-invariance +# cross-check: same σ advantage on both real datasets. const PROFILES = Dict( - "spain" => ("spain", ["214.csv"], 7, 52, 49_442_844), - "usa" => ("usa", ["310.csv","311.csv"], 480, 817, 335_000_000), + "spain" => ("spain", ["opencellid/214.csv"], 7, 52, 49_442_844), + "usa" => ("usa", ["opencellid/310.csv","opencellid/311.csv"], 480, 817, 335_000_000), + "usa_asr" => ("usa", ["asr/310.csv"], 999, 817, 335_000_000), ) function build_topology() haskey(PROFILES, COUNTRY) || error("unknown country $COUNTRY") sub, files, opid, nedge, _pop = PROFILES[COUNTRY] - base = joinpath(@__DIR__, "data", sub, "opencellid") + base = joinpath(@__DIR__, "data", sub) paths = filter(isfile, [joinpath(base, f) for f in files]) - isempty(paths) && error("no OpenCellID data under $base") + isempty(paths) && error("no gNB data under $base for $files") # two_tier: nedge edge UPFs (UL-CL) clustered under NUM_PSA centralized PSAs cfg = SimConfig(1, 2, SCALE, 1, 1, 1, :two_tier, NUM_PSA, 1) topo = DSim.load_and_deploy_network(paths, opid, nedge, joinpath(@__DIR__, "data", sub), cfg)