Examples

Basic Simulation

 1"""Basic example demonstrating PyTwinNet usage.
 2
 3Steps
 4-----
 51. Build a DigitalTwin with a simple Network (3 nodes).
 62. Define a Scenario that moves one node.
 73. Run the Simulator and print final node positions.
 8"""
 9
10from pytwinnet import DigitalTwin, Network, WirelessNode, TransceiverProperties
11from pytwinnet.physics import Environment, FreeSpacePathLoss
12from pytwinnet.simulation import Scenario, MoveNodeEvent, Simulator
13
14
15def main() -> None:
16    # 1) Create twin and environment
17    twin = DigitalTwin()
18    twin.set_environment(Environment(dimensions_m=(200.0, 200.0, 30.0)))
19    twin.set_propagation_model(FreeSpacePathLoss())
20
21    # 2) Build a simple network
22    net = Network()
23    net.add_node(WirelessNode(node_id="gNB-1", position=(0.0, 0.0, 10.0),
24                              transceiver_properties=TransceiverProperties(transmit_power_dbm=30.0)))
25    net.add_node(WirelessNode(node_id="UE-1", position=(10.0, 0.0, 1.5)))
26    net.add_node(WirelessNode(node_id="UE-2", position=(0.0, 20.0, 1.5)))
27    twin.network = net
28
29    # 3) Define a scenario: move UE-1
30    scenario = Scenario(duration_s=10.0)
31    scenario.add_event(MoveNodeEvent(timestamp=3.0, node_id="UE-1", new_position=(25.0, 5.0, 1.5)))
32
33    # 4) Run simulator (on a snapshot to protect live twin)
34    sim = Simulator(twin)
35    final_twin = sim.run(scenario, copy_twin=True)
36
37    # 5) Print final positions
38    for node in final_twin.network.list_nodes():
39        print(f"{node.node_id}: position={node.position}, tx_power={node.transceiver_properties.transmit_power_dbm:.1f} dBm")
40
41
42if __name__ == "__main__":
43    main()

What-If Throughput

 1from pytwinnet import DigitalTwin, Network, WirelessNode, TransceiverProperties
 2from pytwinnet.physics import Environment, FreeSpacePathLoss
 3from pytwinnet.simulation import Scenario, MoveNodeEvent, what_if
 4from pytwinnet.optimization.objective import SumThroughputObjective
 5
 6def build_twin():
 7    twin = DigitalTwin()
 8    twin.set_environment(Environment(dimensions_m=(300.0, 300.0, 30.0)))
 9    twin.set_propagation_model(FreeSpacePathLoss())
10    net = Network()
11    net.add_node(WirelessNode(node_id="gNB-1", position=(0.0, 0.0, 10.0),
12                              transceiver_properties=TransceiverProperties(transmit_power_dbm=30.0, antenna_gain_dbi=5.0)))
13    for i, pos in enumerate([(30.0, 0.0, 1.5), (0.0, 40.0, 1.5), (25.0, 25.0, 1.5), (60.0, 50.0, 1.5)]):
14        net.add_node(WirelessNode(node_id=f"UE-{i+1}", position=pos))
15    twin.network = net
16    return twin
17
18def scenario_move_gnb(new_position):
19    s = Scenario(duration_s=1.0)
20    s.add_event(MoveNodeEvent(timestamp=0.5, node_id="gNB-1", new_position=new_position))
21    return s
22
23def main():
24    twin = build_twin()
25    obj = SumThroughputObjective(tx_id="gNB-1", efficiency=0.75)
26    resA = what_if(twin, scenario_move_gnb((20.0, 20.0, 10.0)), objective=obj)
27    resB = what_if(twin, scenario_move_gnb((80.0, 80.0, 10.0)), objective=obj)
28    print("Candidate A score (bps):", f"{resA['score']:.2f}")
29    print("Candidate B score (bps):", f"{resB['score']:.2f}")
30    print("\nFinal positions for Candidate A:")
31    for n in resA["twin"].network.list_nodes():
32        print(f"  {n.node_id}: {n.position}")
33
34if __name__ == "__main__":
35    main()

SINR Heatmap

 1
 2from pytwinnet import DigitalTwin, Network, WirelessNode, TransceiverProperties
 3from pytwinnet.physics import Environment, FreeSpacePathLoss
 4from pytwinnet.visualization import sinr_heatmap_2d
 5
 6def main():
 7    twin = DigitalTwin()
 8    twin.set_environment(Environment(dimensions_m=(200, 200, 20)))
 9    twin.set_propagation_model(FreeSpacePathLoss())
10    net = Network()
11    net.add_node(WirelessNode(node_id="gNB-1", position=(50.0, 50.0, 10.0),
12                              transceiver_properties=TransceiverProperties(transmit_power_dbm=30.0, antenna_gain_dbi=5.0),
13                              metadata={"role":"gNB"}))
14    net.add_node(WirelessNode(node_id="gNB-2", position=(150.0, 150.0, 10.0),
15                              transceiver_properties=TransceiverProperties(transmit_power_dbm=30.0, antenna_gain_dbi=5.0),
16                              metadata={"role":"gNB"}))
17    twin.network = net
18    sinr_heatmap_2d(twin, tx_id="gNB-1", interferer_ids=["gNB-2"],
19                    xlim=(0, 200), ylim=(0, 200), resolution=60, show=False)
20    print("Generated SINR heatmap (not displayed in non-GUI env).")
21
22if __name__ == "__main__":
23    main()

PF Scheduling and Association

 1from pytwinnet import DigitalTwin, Network, WirelessNode, TransceiverProperties
 2from pytwinnet.physics import Environment, FreeSpacePathLoss
 3from pytwinnet.scheduling import max_rsrp_association, proportional_fair_allocation
 4
 5def main():
 6    twin = DigitalTwin()
 7    twin.set_environment(Environment(dimensions_m=(300, 300, 30)))
 8    twin.set_propagation_model(FreeSpacePathLoss())
 9
10    net = Network()
11    net.add_node(WirelessNode("gNB-1", position=(50, 50, 10),
12                              transceiver_properties=TransceiverProperties(transmit_power_dbm=32, antenna_gain_dbi=5),
13                              metadata={"role": "gNB"}))
14    net.add_node(WirelessNode("gNB-2", position=(250, 250, 10),
15                              transceiver_properties=TransceiverProperties(transmit_power_dbm=32, antenna_gain_dbi=5),
16                              metadata={"role": "gNB"}))
17    for i, pos in enumerate([(60,60,1.5),(80,80,1.5),(120,120,1.5),(200,210,1.5),(240,240,1.5),(260,260,1.5)]):
18        net.add_node(WirelessNode(f"UE-{i+1}", position=pos))
19    twin.network = net
20
21    tx_ids = ["gNB-1", "gNB-2"]
22    ue_ids = [f"UE-{i+1}" for i in range(6)]
23    assoc = max_rsrp_association(twin, tx_ids, ue_ids)
24    print("Association (UE -> TX):", assoc)
25
26    sched = proportional_fair_allocation(twin, assoc, rb_count=20)
27    for tx, ues in sched.items():
28        print(f"{tx}: scheduled RBs ->", ues[:10], "...")
29
30if __name__ == "__main__":
31    main()

Handover (Hysteresis + Time-to-Trigger)

 1from pytwinnet import DigitalTwin, Network, WirelessNode, TransceiverProperties
 2from pytwinnet.physics import Environment, FreeSpacePathLoss
 3from pytwinnet.handover import HandoverController
 4
 5def main():
 6    twin = DigitalTwin()
 7    twin.set_environment(Environment(dimensions_m=(300,300,30)))
 8    twin.set_propagation_model(FreeSpacePathLoss())
 9
10    net = Network()
11    net.add_node(WirelessNode("gNB-1", position=(50,150,10),
12                              transceiver_properties=TransceiverProperties(transmit_power_dbm=32, antenna_gain_dbi=5),
13                              metadata={"role":"gNB"}))
14    net.add_node(WirelessNode("gNB-2", position=(250,150,10),
15                              transceiver_properties=TransceiverProperties(transmit_power_dbm=32, antenna_gain_dbi=5),
16                              metadata={"role":"gNB"}))
17    net.add_node(WirelessNode("UE-1", position=(60,150,1.5)))
18    twin.network = net
19
20    ho = HandoverController(hysteresis_db=3.0, time_to_trigger_s=0.64)
21    serving = "gNB-1"
22    t = 0.0
23    for step in range(11):
24        x = 60 + step * 20      # move east every step
25        ue = twin.network.get_node_by_id("UE-1")
26        ue.move_to((x, 150, 1.5))
27        serving_new = ho.step(twin, "UE-1", serving, timestamp_s=t)
28        print(f"t={t:.2f}s, UE-1 at x={x}, serving={serving} -> {serving_new}")
29        serving = serving_new
30        t += 0.2
31
32if __name__ == "__main__":
33    main()

RIS-Enhanced Propagation (Toy)

 1from pytwinnet import DigitalTwin, Network, WirelessNode, TransceiverProperties
 2from pytwinnet.physics import Environment, FreeSpacePathLoss
 3from pytwinnet.physics.ris import RISPanel, RISAugmentedModel
 4from pytwinnet.physics.link_budget import rx_power_dbm
 5
 6def main():
 7    base = FreeSpacePathLoss()
 8    ris = RISPanel(position=(100, 50, 10), gain_db=12.0)
 9    twin = DigitalTwin()
10    twin.set_environment(Environment(dimensions_m=(200, 100, 30)))
11    twin.set_propagation_model(RISAugmentedModel(base, ris, extra_loss_db=3.0))
12
13    net = Network()
14    net.add_node(WirelessNode("gNB-1", position=(20, 50, 10),
15                              transceiver_properties=TransceiverProperties(transmit_power_dbm=30, antenna_gain_dbi=5),
16                              metadata={"role":"gNB"}))
17    net.add_node(WirelessNode("UE-1", position=(180, 50, 1.5)))
18    twin.network = net
19
20    pm = twin.propagation_model; env = twin.environment
21    tx = twin.network.get_node_by_id("gNB-1")
22    rx = twin.network.get_node_by_id("UE-1")
23
24    pl_ris = pm.calculate_path_loss(tx, rx, env)
25    prx_ris = rx_power_dbm(tx.transceiver_properties.transmit_power_dbm,
26                           tx.transceiver_properties.antenna_gain_dbi,
27                           rx.transceiver_properties.antenna_gain_dbi,
28                           pl_ris)
29
30    direct_pm = FreeSpacePathLoss()
31    pl_direct = direct_pm.calculate_path_loss(tx, rx, env)
32    prx_direct = rx_power_dbm(tx.transceiver_properties.transmit_power_dbm,
33                              tx.transceiver_properties.antenna_gain_dbi,
34                              rx.transceiver_properties.antenna_gain_dbi,
35                              pl_direct)
36
37    print(f"Direct-only RSRP: {prx_direct:.2f} dBm | RIS-augmented RSRP: {prx_ris:.2f} dBm (higher is better)")
38
39if __name__ == "__main__":
40    main()

Config-Driven Experiment

python -m pytwinnet.cli run configs/het_net_placement.yaml