Q2NS dev
ns-3 module
Loading...
Searching...
No Matches
q2ns-4-multipartite-scaling-example.cc
Go to the documentation of this file.
1/*-----------------------------------------------------------------------------
2 * Q2NS - Quantum Network Simulator
3 * Copyright (c) 2026 quantuminternet.it
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 as
7 * published by the Free Software Foundation.
8 *---------------------------------------------------------------------------*/
9/**
10 * @file q2ns-4-multipartite-scaling-example.cc
11 * @brief Multipartite entanglement distribution and backend scaling demo
12 *
13 * Demonstrates:
14 * - creating a 1D cluster state on a central node
15 * - distributing one qubit to each remote node
16 * - comparing wall-clock runtime across backends
17 *
18 * Tunable parameters:
19 * --numNodes total number of nodes (>= 2)
20 * --backend ket | dm | stab
21 * --trials repeated runs for mean/stdev wall-clock timing
22 *
23 * Example:
24 * ./ns3 run q2ns-4-multipartite-scaling-example -- --numNodes=16 --backend=stab --trials=5
25 *---------------------------------------------------------------------------*/
26
27#include "ns3/core-module.h"
28#include "ns3/simulator.h"
29
30#include "ns3/q2ns-netcontroller.h"
31#include "ns3/q2ns-qnode.h"
32#include "ns3/q2ns-qubit.h"
33
34#include <algorithm>
35#include <chrono>
36#include <cmath>
37#include <cstdint>
38#include <iomanip>
39#include <iostream>
40#include <memory>
41#include <numeric>
42#include <string>
43#include <vector>
44
45using namespace ns3;
46using namespace q2ns;
47
48namespace {
49
51 std::vector<double> valuesMs;
52
53 void Add(double x) {
54 valuesMs.push_back(x);
55 }
56
57 double Mean() const {
58 if (valuesMs.empty())
59 return 0.0;
60 double s = std::accumulate(valuesMs.begin(), valuesMs.end(), 0.0);
61 return s / static_cast<double>(valuesMs.size());
62 }
63
64 double StdDev() const {
65 if (valuesMs.size() < 2)
66 return 0.0;
67 const double mean = Mean();
68 double accum = 0.0;
69 for (double x : valuesMs) {
70 const double d = x - mean;
71 accum += d * d;
72 }
73 return std::sqrt(accum / static_cast<double>(valuesMs.size() - 1));
74 }
75
76 double Median() const {
77 if (valuesMs.empty())
78 return 0.0;
79
80 std::vector<double> tmp = valuesMs;
81 std::sort(tmp.begin(), tmp.end());
82
83 const size_t n = tmp.size();
84 if (n % 2 == 1) {
85 return tmp[n / 2];
86 }
87 return 0.5 * (tmp[n / 2 - 1] + tmp[n / 2]);
88 }
89};
90
91
92double RunOnce(uint32_t numNodes, const std::string& backend, bool verbose) {
93 auto t0 = std::chrono::steady_clock::now();
94
95 NetController net;
96 net.SetQStateBackend(backend);
97
98 std::vector<Ptr<QNode>> nodes;
99 nodes.reserve(numNodes);
100
101 for (uint32_t i = 0; i < numNodes; ++i) {
102 nodes.push_back(net.CreateNode());
103 }
104
105 Ptr<QNode> center = nodes[0];
106
107 for (uint32_t i = 1; i < numNodes; ++i) {
108 auto ch = net.InstallQuantumLink(center, nodes[i]);
109 ch->SetAttribute("Delay", TimeValue(NanoSeconds(10)));
110 }
111
112
113 for (uint32_t i = 1; i < numNodes; ++i) {
114 Ptr<QNode> node = nodes[i];
115 node->SetRecvCallback([node](std::shared_ptr<Qubit> q) { node->Measure(q); });
116 }
117
118 auto t1 = std::chrono::steady_clock::now();
119
120 Simulator::Schedule(NanoSeconds(1), [center, &nodes, numNodes]() {
121 std::vector<std::shared_ptr<Qubit>> qs;
122 qs.reserve(numNodes);
123
124 for (uint32_t i = 0; i < numNodes; ++i) {
125 qs.push_back(center->CreateQubit());
126 }
127
128 // Prepare a 1D cluster state:
129 for (uint32_t i = 0; i < numNodes; ++i) {
130 center->Apply(gates::H(), {qs[i]});
131 }
132
133 for (uint32_t i = 0; i + 1 < numNodes; ++i) {
134 center->Apply(gates::CZ(), {qs[i], qs[i + 1]});
135 }
136
137 // Distribute one qubit to each remote node
138 for (uint32_t i = 1; i < numNodes; ++i) {
139 const bool ok = center->Send(qs[i], nodes[i]->GetId());
140 if (!ok) {
141 std::cerr << "[WARN] Send to node " << i << " failed\n";
142 }
143 }
144
145 // Center can measure its qubit after sending
146 // This will run at 20 ns since the scheduled time is relative to the current simulation time
147 // (Simulator::Now()). This overall lambda runs at 1 ns and then this one below for measurement
148 // runs 19 ns after that.
149 Simulator::Schedule(NanoSeconds(19), [centerQubit = qs[0], center]() {
150 center->Measure(centerQubit, Basis::Z);
151 });
152 });
153
154 // Leave plenty of time for all deliveries
155 Simulator::Stop(MicroSeconds(10));
156 Simulator::Run();
157 Simulator::Destroy();
158
159 auto t2 = std::chrono::steady_clock::now();
160
161 const double configMs = std::chrono::duration<double, std::milli>(t1 - t0).count();
162 const double simMs = std::chrono::duration<double, std::milli>(t2 - t1).count();
163 const double totalMs = std::chrono::duration<double, std::milli>(t2 - t0).count();
164
165 if (verbose) {
166 std::cout << "[RUN] config=" << std::fixed << std::setprecision(3) << configMs << " ms"
167 << " sim=" << simMs << " ms"
168 << " total=" << totalMs << " ms\n";
169 }
170
171 return totalMs;
172}
173
174} // namespace
175
176int main(int argc, char* argv[]) {
177 uint32_t numNodes = 8;
178 uint32_t trials = 3;
179 std::string backend = "stab";
180 uint32_t seed = 1;
181 uint32_t run = 1;
182 bool verbose = false;
183
184 CommandLine cmd;
185 cmd.AddValue("numNodes", "Total number of nodes (>= 2)", numNodes);
186 cmd.AddValue("trials", "Number of repeated runs", trials);
187 cmd.AddValue("backend", "ket | dm | stab", backend);
188 cmd.AddValue("seed", "ns-3 RNG seed", seed);
189 cmd.AddValue("run", "ns-3 RNG run number", run);
190 cmd.AddValue("verbose", "Print per-trial timing details", verbose);
191 cmd.Parse(argc, argv);
192
193 if (numNodes < 2) {
194 NS_ABORT_MSG("--numNodes must be at least 2");
195 }
196
197 RngSeedManager::SetSeed(seed);
198
199 std::cout << "[DEMO] Multipartite entanglement distribution starting\n";
200 std::cout << " nodes = " << numNodes << "\n";
201 std::cout << " backend = " << backend << "\n";
202 std::cout << " trials = " << trials << "\n";
203
204 RunningStats totalStats;
205
206 for (uint32_t t = 0; t < trials; ++t) {
207 RngSeedManager::SetRun(run + t);
208 if (verbose) {
209 std::cout << "\n[TRIAL " << (t + 1) << "/" << trials << "]\n";
210 }
211
212 totalStats.Add(RunOnce(numNodes, backend, verbose));
213 }
214
215 std::cout << "\n[RESULT] backend=" << backend << " nodes=" << numNodes
216 << " mean_total_ms=" << std::fixed << std::setprecision(3) << totalStats.Mean()
217 << " median_total_ms=" << totalStats.Median() << " stddev_ms=" << totalStats.StdDev()
218 << "\n";
219
220 std::cout << "[DONE] Multipartite entanglement distribution finished\n";
221 return 0;
222}
Main user-facing facade for creating and configuring a quantum network.
ns3::Ptr< QNode > CreateNode(const std::string &label="")
Create a QNode with an optional human-readable label.
ns3::Ptr< QChannel > InstallQuantumLink(ns3::Ptr< QNode > a, ns3::Ptr< QNode > b)
Install a duplex quantum link between two nodes.
void SetQStateBackend(QStateBackend b)
Set the default backend used for newly created quantum states.
double RunOnce(uint32_t numNodes, const std::string &backend, bool verbose)
QGate CZ(ns3::Time d=ns3::Seconds(0))
Return the CZ gate descriptor.
Definition q2ns-qgate.h:419
QGate H(ns3::Time d=ns3::Seconds(0))
Return the Hadamard gate descriptor.
Definition q2ns-qgate.h:383
int main()