Rename the simulator and report from pd to blend
The study started as a peering-degree question and grew well past it: propagation, adversary exposure, deanonymization and time-to-link, reliability under uniform and correlated churn, messaging redundancy, and cover traffic. The pd name no longer describes it. tools/simulators/blend/pd/ -> tools/simulators/blend/, package src/pd -> src/blend, and reports/blend/pd/ -> reports/blend/. Moved with git mv so history follows. The text substitutions are deliberately narrow. pd is also the conventional pandas alias, and pandas genuinely has a pd.plotting submodule, so a blanket pd. -> blend. rewrite would have corrupted four files. Only package-unambiguous forms were changed: from pd.X, -m pd.X, pd.<our module>, PD_BYTES_BUDGET, src/pd, and the pyproject name. All four import pandas as pd lines are untouched and verified. Both READMEs reframed: peering degree is now presented as the primary axis that ties the others together rather than as the subject, and the relative links, which lost a directory level in the move, are corrected. Verified after the move: ruff clean, 101 tests, 45 verify anchors, make targets, the script shims, an end-to-end smoke run, and data/report_numbers.py still reproducing the report tables from the checked-in evidence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ -1,8 +1,8 @@
|
||||
# Peering degree in the Blend network — a Monte-Carlo study
|
||||
# The Blend network — a Monte-Carlo study
|
||||
|
||||
*Static-graph network simulation of the Blend message cascade. Simulator: [`pd`](../../../tools/simulators/blend/pd). All delays in **milliseconds**; the free-running mix clock's maximum interval (`max_blend_delay`) is in **whole seconds**. Adversary and deanonymization metrics are exact at every network size; propagation is Monte-Carlo over random senders.*
|
||||
*Network simulation of the Blend message cascade. Simulator: [`blend`](../../tools/simulators/blend). All delays in **milliseconds**; the free-running mix clock's maximum interval (`max_blend_delay`) is in **whole seconds**. Adversary and deanonymization metrics are exact at every network size; propagation is Monte-Carlo over random senders.*
|
||||
|
||||
This report quantifies how a node's **peering degree** — the number of symmetric peers it keeps — trades off four properties of the Blend network at once: how fast a message propagates, how much of the network an adversary observes, how often a message is deanonymized, and how reliably messages are delivered when a fraction of nodes go dark. The headline is a single tension: **raising the peering degree improves propagation speed, eclipse resistance, and churn resilience, but *worsens* observation and sender deanonymization.** The anonymity axis has its own, degree-independent control — the **blend-path length** — so the two knobs separate cleanly: set the degree for the transport goals, set the path length for the anonymity goal. Two further questions follow from the deanonymization rates: *how long* an adversary needs to link an emitter to its messages and to learn its stake — which scales inversely with the node's own stake — and how **messaging redundancy** (sending each message over several independent cascades) trades reliability against anonymity, amplifying both by the very same factor.
|
||||
This report measures the Blend network end to end: how fast a message propagates, how much of the network an adversary observes, how often a message is deanonymized and how quickly a node can be linked to one, how reliably messages are delivered when nodes go dark or whole regions fail, and what cover traffic buys. **Peering degree** is the first axis and the one that ties the others together, because it trades off several of these at once: the headline is a single tension: **raising the peering degree improves propagation speed, eclipse resistance, and churn resilience, but *worsens* observation and sender deanonymization.** The anonymity axis has its own, degree-independent control — the **blend-path length** — so the two knobs separate cleanly: set the degree for the transport goals, set the path length for the anonymity goal. Two further questions follow from the deanonymization rates: *how long* an adversary needs to link an emitter to its messages and to learn its stake — which scales inversely with the node's own stake — and how **messaging redundancy** (sending each message over several independent cascades) trades reliability against anonymity, amplifying both by the very same factor.
|
||||
|
||||
## Headline
|
||||
|
||||
@ -392,7 +392,7 @@ Because `s_max` is expressed against `D̂`, an estimator that runs low tightens
|
||||
<a id="s6"></a>
|
||||
## 6. Reproducibility
|
||||
|
||||
The simulator, configs, and analytic checks live in [`tools/simulators/blend/pd`](../../../tools/simulators/blend/pd). From that directory: `make install`, then `make sweep` runs the main grid (`configs/default.yaml`: N up to 10⁵, degree 3–16, 1–5 blend hops, `f_adv` up to 0.5, unresponsive fractions to 0.5, all three placement modes, 8 topology seeds) into `runs/<timestamp>_default/`, writing three tables — `propagation.parquet`, `adversary.parquet`, and `deanon.parquet` — and rendering the figures. `make sweep-fullscale` extends the exact metrics to 10⁶ nodes. The messaging-redundancy study (§3.8) and the linkability figures come from `configs/redundancy.yaml` (`python -m pd.sweep --config configs/redundancy.yaml`), which sweeps `redundancy` ∈ {1, 2, 3, 4} alongside the churn and adversary grids; the churn-threshold study (§3.5, Fig 20) comes from `configs/percolation.yaml`, which walks the unresponsive fraction to 0.9 so each degree's collapse can be located against `u_c = 1 − 1/(degree − 1)`; and the correlated-outage study (§3.9, Fig 22) from `configs/correlated-churn.yaml` (`make correlated-churn`), which partitions the network into failure domains and runs both churn modes on the same topologies. `make sweep-fullscale` produces the 10⁶ scaling check described in §5; and the cover-traffic study (§3.10, Figs 23–24) comes from `configs/cover-traffic.yaml`, which sweeps the emission rate over three decades against three release delays and pairs each timeline with the epoch-scale emission budget. Round counts in all three configs are set for statistical resolution, not speed — see the sampling-error note in §5. `make verify` runs the analytic anchors (d-regularity; `observed ≈ 1 − (1 − f)^degree`; `eclipsed ≈ f^degree`; delivery `≈ (1 − u)^blend_hops`; both deanonymization rates against a direct Monte-Carlo of the same draw; and — check 6 — `deanon_R` / `delivery_R = 1 − (1 − x)^R` for R independent cascades and the time-to-link geometric law), and `make test` the unit suite (`test_linkability.py` covers the time-to-link and stake formulae). The time-to-link and stake-inference curves are computed by `pd.linkability` from these exact rates.
|
||||
The simulator, configs, and analytic checks live in [`tools/simulators/blend`](../../tools/simulators/blend). From that directory: `make install`, then `make sweep` runs the main grid (`configs/default.yaml`: N up to 10⁵, degree 3–16, 1–5 blend hops, `f_adv` up to 0.5, unresponsive fractions to 0.5, all three placement modes, 8 topology seeds) into `runs/<timestamp>_default/`, writing three tables — `propagation.parquet`, `adversary.parquet`, and `deanon.parquet` — and rendering the figures. `make sweep-fullscale` extends the exact metrics to 10⁶ nodes. The messaging-redundancy study (§3.8) and the linkability figures come from `configs/redundancy.yaml` (`python -m blend.sweep --config configs/redundancy.yaml`), which sweeps `redundancy` ∈ {1, 2, 3, 4} alongside the churn and adversary grids; the churn-threshold study (§3.5, Fig 20) comes from `configs/percolation.yaml`, which walks the unresponsive fraction to 0.9 so each degree's collapse can be located against `u_c = 1 − 1/(degree − 1)`; and the correlated-outage study (§3.9, Fig 22) from `configs/correlated-churn.yaml` (`make correlated-churn`), which partitions the network into failure domains and runs both churn modes on the same topologies. `make sweep-fullscale` produces the 10⁶ scaling check described in §5; and the cover-traffic study (§3.10, Figs 23–24) comes from `configs/cover-traffic.yaml`, which sweeps the emission rate over three decades against three release delays and pairs each timeline with the epoch-scale emission budget. Round counts in all three configs are set for statistical resolution, not speed — see the sampling-error note in §5. `make verify` runs the analytic anchors (d-regularity; `observed ≈ 1 − (1 − f)^degree`; `eclipsed ≈ f^degree`; delivery `≈ (1 − u)^blend_hops`; both deanonymization rates against a direct Monte-Carlo of the same draw; and — check 6 — `deanon_R` / `delivery_R = 1 − (1 − x)^R` for R independent cascades and the time-to-link geometric law), and `make test` the unit suite (`test_linkability.py` covers the time-to-link and stake formulae). The time-to-link and stake-inference curves are computed by `blend.linkability` from these exact rates.
|
||||
|
||||
The figures of record for this report are the copies checked in under [`report-figures/`](report-figures); the simulator does not commit its own generated figures. To regenerate: run the sweeps above, then copy `runs/<…>/figures/*.png` into `report-figures/`.
|
||||
|
||||
@ -17,7 +17,7 @@ Each run directory holds the three tables the simulator writes: `propagation.par
|
||||
| `cover-traffic/` | `configs/cover-traffic.yaml` | 900 s timeline × 4 seeds | §3.10 — blending, mixing, and the emission-quota stake ceiling. Carries a fourth table, `traffic.parquet` |
|
||||
|
||||
The linkability results (§3.6–§3.7) and both deanonymization rates are closed forms over these
|
||||
tables rather than separate measurements, so they have no run of their own — `pd.linkability`
|
||||
tables rather than separate measurements, so they have no run of their own — `blend.linkability`
|
||||
derives them and `make verify` checks them against Monte-Carlo.
|
||||
|
||||
## Regenerating the report's numbers
|
||||
@ -33,7 +33,7 @@ runs instead.
|
||||
|
||||
## Regenerating the data itself
|
||||
|
||||
From [`tools/simulators/blend/pd`](../../../../tools/simulators/blend/pd): `make sweep`,
|
||||
From [`tools/simulators/blend`](../../../tools/simulators/blend): `make sweep`,
|
||||
`make redundancy`, `make percolation`, `make correlated-churn`, `make sweep-fullscale`. Results
|
||||
land in that simulator's `runs/<timestamp>_<label>/`. Note that the seed streams depend on the
|
||||
configuration, so re-running reproduces the *statistics*, not bit-identical numbers, unless the
|
||||
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 221 KiB |
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 208 KiB |
|
Before Width: | Height: | Size: 203 KiB After Width: | Height: | Size: 203 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 222 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 144 KiB |
@ -21,28 +21,28 @@ $(STAMP): pyproject.toml
|
||||
install: $(STAMP)
|
||||
|
||||
smoke: $(STAMP) ## fast end-to-end (seconds): tiny N, few rounds/seeds
|
||||
$(PY) -m pd.sweep --config configs/smoke.yaml
|
||||
$(PY) -m blend.sweep --config configs/smoke.yaml
|
||||
|
||||
sweep: $(STAMP)
|
||||
$(PY) -m pd.sweep --config configs/default.yaml
|
||||
$(PY) -m blend.sweep --config configs/default.yaml
|
||||
|
||||
sweep-fullscale: $(STAMP)
|
||||
$(PY) -m pd.sweep --config configs/fullscale.yaml
|
||||
$(PY) -m blend.sweep --config configs/fullscale.yaml
|
||||
|
||||
redundancy: $(STAMP) ## messaging redundancy R=1..4 (delivery vs deanonymization, time-to-link)
|
||||
$(PY) -m pd.sweep --config configs/redundancy.yaml
|
||||
$(PY) -m blend.sweep --config configs/redundancy.yaml
|
||||
|
||||
percolation: $(STAMP) ## churn threshold: coverage collapse at u_c = 1 - 1/(degree-1)
|
||||
$(PY) -m pd.sweep --config configs/percolation.yaml
|
||||
$(PY) -m blend.sweep --config configs/percolation.yaml
|
||||
|
||||
correlated-churn: $(STAMP) ## correlated AS/region outages vs uniform churn, matched fractions
|
||||
$(PY) -m pd.sweep --config configs/correlated-churn.yaml
|
||||
$(PY) -m blend.sweep --config configs/correlated-churn.yaml
|
||||
|
||||
figures: $(STAMP) ## make figures RUN=runs/<dir>
|
||||
$(PY) -m pd.plotting.make_figures --run $(RUN)
|
||||
$(PY) -m blend.plotting.make_figures --run $(RUN)
|
||||
|
||||
verify: $(STAMP)
|
||||
$(PY) -m pd.verify
|
||||
$(PY) -m blend.verify
|
||||
|
||||
test: $(STAMP)
|
||||
$(PY) -m pytest
|
||||
@ -1,6 +1,9 @@
|
||||
# pd — peering-degree Monte-Carlo graph simulator
|
||||
# blend — a Monte-Carlo simulator for the Blend network
|
||||
|
||||
Quantifies how a node's **peering degree** trades off, in the Blend network:
|
||||
Measures the Blend network on a seeded peer graph: propagation, adversary exposure,
|
||||
deanonymization, reliability under churn, messaging redundancy and cover traffic.
|
||||
Peering degree is the primary study axis and the one that ties the rest together —
|
||||
it trades off, simultaneously:
|
||||
|
||||
- **propagation speed** — the full delay (ms) of a message: a random sender routes it along a
|
||||
`blend_hops`-relay Blend path (each relay a free-running timed-release mix node) and the last
|
||||
@ -62,14 +65,14 @@ adversary metrics are exact at every N).
|
||||
random node set. Clustered failure leaves the survivors fully connected (`frac_reached_live`
|
||||
stays ~1) while stranding the dead domains (`frac_reached` falls); see `configs/correlated-churn.yaml`.
|
||||
Regions are failure and peering domains only — link latency does not depend on them.
|
||||
- **Linkability over time** (`pd.linkability`): given the rates above and an emission cadence (one
|
||||
- **Linkability over time** (`blend.linkability`): given the rates above and an emission cadence (one
|
||||
node emits per 30 s slot, chosen ∝ stake), the module derives the *time to link* an emitter
|
||||
(`≈ 30 s·ln(1/(1−α))/(stake·q)`, inverse in stake) and the *time to learn its stake* to a threshold
|
||||
from the count of attributable observations. See `configs/redundancy.yaml` and the report.
|
||||
|
||||
## Quick start
|
||||
```
|
||||
make install # or reuse a sibling venv: PYTHONPATH=src <python> -m pd.sweep ...
|
||||
make install # or reuse a sibling venv: PYTHONPATH=src <python> -m blend.sweep ...
|
||||
make smoke # fast end-to-end (every code path + 19 of 21 figure builders)
|
||||
make verify # analytic checks (closed forms + graph invariants)
|
||||
make test # unit tests
|
||||
@ -93,7 +96,7 @@ unresponsive fraction (with the `u_c = 1-1/(degree-1)` threshold), the deanonymi
|
||||
time-to-link / stake-inference / redundancy curves derived from them by `linkability`.
|
||||
|
||||
## Layout
|
||||
`src/pd/`: `graph` (matching-union CSR d-regular), `propagation` (Blend cascade), `mixclock`
|
||||
`src/blend/`: `graph` (matching-union CSR d-regular), `propagation` (Blend cascade), `mixclock`
|
||||
(release-clock residual), `adversary` (exact observation/eclipse + deanonymization + placement),
|
||||
`config`/`engine`/`sweep`/`metrics`, `plotting`. `configs/` sweeps, `tests/`, `scripts/` shims.
|
||||
Reports of record live outside the sim at `reports/blend/pd/`.
|
||||
Reports of record live outside the sim at `reports/blend/`.
|
||||
@ -3,7 +3,7 @@ requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "pd"
|
||||
name = "blend"
|
||||
version = "0.1.0"
|
||||
description = "Peering-degree Monte-Carlo graph simulator for the Blend network"
|
||||
requires-python = ">=3.11"
|
||||
@ -22,12 +22,12 @@ dependencies = [
|
||||
dev = ["pytest", "pytest-xdist", "ruff", "mypy"]
|
||||
|
||||
[project.scripts]
|
||||
pd-sweep = "pd.sweep:main"
|
||||
pd-verify = "pd.verify:main"
|
||||
pd-figures = "pd.plotting.make_figures:main"
|
||||
pd-sweep = "blend.sweep:main"
|
||||
pd-verify = "blend.verify:main"
|
||||
pd-figures = "blend.plotting.make_figures:main"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/pd"]
|
||||
packages = ["src/blend"]
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Shim: add src/ to sys.path, then run pd.plotting.make_figures:main."""
|
||||
"""Shim: add src/ to sys.path, then run blend.plotting.make_figures:main."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||
|
||||
from pd.plotting.make_figures import main # noqa: E402
|
||||
from blend.plotting.make_figures import main # noqa: E402
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Shim: add src/ to sys.path, then run pd.sweep:main."""
|
||||
"""Shim: add src/ to sys.path, then run blend.sweep:main."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||
|
||||
from pd.sweep import main # noqa: E402
|
||||
from blend.sweep import main # noqa: E402
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Shim: add src/ to sys.path, then run pd.verify:main."""
|
||||
"""Shim: add src/ to sys.path, then run blend.verify:main."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||
|
||||
from pd.verify import main # noqa: E402
|
||||
from blend.verify import main # noqa: E402
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@ -6,7 +6,7 @@ sampled single-source distance matrices (``S x N`` or ``(blend_hops+1) x N``), w
|
||||
would exceed its budget, so an under-sized config fails with a clear message instead of freezing
|
||||
the machine.
|
||||
|
||||
Budget (``budget_bytes``): ``PD_BYTES_BUDGET`` > 0 -> that many bytes (the sweep sets this to
|
||||
Budget (``budget_bytes``): ``BLEND_BYTES_BUDGET`` > 0 -> that many bytes (the sweep sets this to
|
||||
each worker's RAM share); otherwise ``DEFAULT_BUDGET_FRAC`` of physical RAM.
|
||||
"""
|
||||
|
||||
@ -38,7 +38,7 @@ def total_ram_bytes() -> int:
|
||||
def budget_bytes() -> int:
|
||||
"""Per-process byte budget for a single big array (see module docstring)."""
|
||||
try:
|
||||
explicit = int(os.environ.get("PD_BYTES_BUDGET", "0"))
|
||||
explicit = int(os.environ.get("BLEND_BYTES_BUDGET", "0"))
|
||||
except ValueError:
|
||||
explicit = 0
|
||||
if explicit > 0:
|
||||
@ -2,9 +2,9 @@ from itertools import combinations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from pd.adversary import _greedy_coverage, adversary_metrics, place_adversary
|
||||
from pd.config import SimConfig
|
||||
from pd.graph import Graph, build_graph
|
||||
from blend.adversary import _greedy_coverage, adversary_metrics, place_adversary
|
||||
from blend.config import SimConfig
|
||||
from blend.graph import Graph, build_graph
|
||||
|
||||
|
||||
def _cycle4():
|
||||
@ -2,7 +2,7 @@ import dataclasses
|
||||
|
||||
import pytest
|
||||
|
||||
from pd.config import SimConfig, SweepConfig
|
||||
from blend.config import SimConfig, SweepConfig
|
||||
|
||||
|
||||
def test_key_covers_every_field():
|
||||
@ -6,10 +6,10 @@ uniformly blind to who is adversarial, so both rates are exact (no sampling in p
|
||||
|
||||
import numpy as np
|
||||
|
||||
from pd.adversary import adversary_metrics, deanon_metrics, place_adversary
|
||||
from pd.config import SimConfig
|
||||
from pd.engine import run_graph_cell
|
||||
from pd.graph import build_graph
|
||||
from blend.adversary import adversary_metrics, deanon_metrics, place_adversary
|
||||
from blend.config import SimConfig
|
||||
from blend.engine import run_graph_cell
|
||||
from blend.graph import build_graph
|
||||
|
||||
|
||||
def test_deanon_rate_hand_computed():
|
||||
@ -1,8 +1,8 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from pd.config import SimConfig
|
||||
from pd.graph import build_graph, build_regular_edges
|
||||
from blend.config import SimConfig
|
||||
from blend.graph import build_graph, build_regular_edges
|
||||
|
||||
|
||||
@pytest.mark.parametrize("n,degree", [(10, 1), (10, 2), (10, 3), (100, 4), (100, 7),
|
||||
@ -4,7 +4,7 @@ import math
|
||||
|
||||
import numpy as np
|
||||
|
||||
from pd.linkability import (
|
||||
from blend.linkability import (
|
||||
capture_prob,
|
||||
obs_for_precision,
|
||||
redundant,
|
||||
@ -1,6 +1,6 @@
|
||||
import numpy as np
|
||||
|
||||
from pd.mixclock import mean_residual_ms, mix_wait
|
||||
from blend.mixclock import mean_residual_ms, mix_wait
|
||||
|
||||
|
||||
def test_zero_max_delay_is_zero():
|
||||
@ -1,9 +1,9 @@
|
||||
import numpy as np
|
||||
|
||||
from pd.config import SimConfig
|
||||
from pd.graph import Graph, build_graph
|
||||
from pd.propagation import assign_responsive, blend_round, propagation_metrics
|
||||
from pd.rng import responsive_seedseq, round_seedseq
|
||||
from blend.config import SimConfig
|
||||
from blend.graph import Graph, build_graph
|
||||
from blend.propagation import assign_responsive, blend_round, propagation_metrics
|
||||
from blend.rng import responsive_seedseq, round_seedseq
|
||||
|
||||
|
||||
def _k4(p):
|
||||
@ -206,7 +206,7 @@ def test_redundant_cascades_flood_the_same_component():
|
||||
|
||||
def test_regional_churn_drops_whole_regions_and_matches_the_uniform_count():
|
||||
"""Correlated churn kills failure domains, not scattered nodes -- at the same total count."""
|
||||
from pd.graph import region_of
|
||||
from blend.graph import region_of
|
||||
n, n_regions, u = 1000, 10, 0.3
|
||||
rng = np.random.default_rng(0)
|
||||
mask = assign_responsive(n, u, rng, "regional", n_regions)
|
||||
@ -220,7 +220,7 @@ def test_regional_churn_drops_whole_regions_and_matches_the_uniform_count():
|
||||
|
||||
|
||||
def test_uniform_churn_scatters_across_all_regions():
|
||||
from pd.graph import region_of
|
||||
from blend.graph import region_of
|
||||
n, n_regions, u = 1000, 10, 0.3
|
||||
mask = assign_responsive(n, u, np.random.default_rng(0), "uniform", n_regions)
|
||||
region = region_of(n, n_regions)
|
||||
@ -229,7 +229,7 @@ def test_uniform_churn_scatters_across_all_regions():
|
||||
|
||||
|
||||
def test_region_locality_keeps_peers_inside_the_region_and_stays_d_regular():
|
||||
from pd.graph import build_graph, region_of
|
||||
from blend.graph import build_graph, region_of
|
||||
n, n_regions, degree = 2000, 10, 8
|
||||
region = region_of(n, n_regions)
|
||||
for locality, want in ((0.0, 0.1), (0.5, 0.5), (1.0, 1.0)):
|
||||
@ -244,7 +244,7 @@ def test_region_locality_keeps_peers_inside_the_region_and_stays_d_regular():
|
||||
def test_regional_churn_leaves_survivors_better_connected():
|
||||
"""The point of the correlated model: clustered failure removes whole neighbourhoods and
|
||||
leaves the rest intact, so surviving nodes keep more live peers than under scattered failure."""
|
||||
from pd.graph import build_graph
|
||||
from blend.graph import build_graph
|
||||
n, n_regions, degree, u = 4000, 20, 8, 0.4
|
||||
cfg = SimConfig(n_nodes=n, degree=degree, n_regions=n_regions, region_locality=0.75,
|
||||
graph_seed=0)
|
||||
@ -4,7 +4,7 @@ import math
|
||||
|
||||
import numpy as np
|
||||
|
||||
from pd.quota import (
|
||||
from blend.quota import (
|
||||
alpha_max,
|
||||
assign_stake,
|
||||
emission_quota_per_slot,
|
||||
@ -1,5 +1,5 @@
|
||||
from pd.config import SimConfig
|
||||
from pd.rng import (
|
||||
from blend.config import SimConfig
|
||||
from blend.rng import (
|
||||
graph_seedseq,
|
||||
responsive_seedseq,
|
||||
rng_for,
|
||||
@ -2,9 +2,9 @@
|
||||
|
||||
import numpy as np
|
||||
|
||||
from pd.config import SimConfig
|
||||
from pd.graph import build_graph
|
||||
from pd.traffic import ReleaseClock, simulate_window, traffic_metrics
|
||||
from blend.config import SimConfig
|
||||
from blend.graph import build_graph
|
||||
from blend.traffic import ReleaseClock, simulate_window, traffic_metrics
|
||||
|
||||
|
||||
def _win(n_nodes=2000, degree=8, hops=3, M=3, mult=1.0, slots=600, seed=0):
|
||||