Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ permissions:
contents: read
pages: write
id-token: write
administration: write

concurrency:
group: deploy-docs
Expand All @@ -31,6 +32,8 @@ jobs:
run: zensical build

- uses: actions/configure-pages@v5
with:
enablement: true

- uses: actions/upload-pages-artifact@v3
with:
Expand Down
62 changes: 59 additions & 3 deletions docs/mobility-formal-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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].
Expand Down Expand Up @@ -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 |
Expand Down
49 changes: 33 additions & 16 deletions gen_trajectories.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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/<sub>, 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
Expand Down Expand Up @@ -74,27 +91,27 @@ 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, ",")
print(f, "[", round(g.lon,digits=4), ",", round(g.lat,digits=4), "]")
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")
15 changes: 10 additions & 5 deletions run_national.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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/<sub>, 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)
Expand Down