API Reference

Core

class pytwinnet.core.DigitalTwin(network=<factory>, environment=None, propagation_model=None)[source]

Bases: object

Parameters:
environment: Optional[Environment] = None
network: Network
propagation_model: Optional[PropagationModel] = None
set_environment(env)[source]
Return type:

None

Parameters:

env (Environment)

set_propagation_model(model)[source]
Return type:

None

Parameters:

model (PropagationModel)

snapshot()[source]
Return type:

DigitalTwin

class pytwinnet.core.Network(nodes=<factory>)[source]

Bases: object

Parameters:

nodes (Dict[str, WirelessNode])

add_node(node)[source]
Return type:

None

Parameters:

node (WirelessNode)

get_node_by_id(node_id)[source]
Return type:

Optional[WirelessNode]

Parameters:

node_id (str)

list_nodes()[source]
Return type:

List[WirelessNode]

nodes: Dict[str, WirelessNode]
remove_node(node_id)[source]
Return type:

None

Parameters:

node_id (str)

class pytwinnet.core.TransceiverProperties(transmit_power_dbm=20.0, antenna_gain_dbi=0.0, carrier_frequency_hz=2400000000.0, additional=<factory>)[source]

Bases: object

Parameters:
additional: Dict[str, Any]
antenna_gain_dbi: float = 0.0
carrier_frequency_hz: float = 2400000000.0
transmit_power_dbm: float = 20.0
class pytwinnet.core.WirelessNode(node_id, position=(0.0, 0.0, 0.0), transceiver_properties=<factory>, mobility_model=None, metadata=<factory>)[source]

Bases: object

Parameters:
metadata: Dict[str, Any]
mobility_model: Optional[object] = None
move_to(new_position)[source]
Return type:

None

Parameters:

new_position (Tuple[float, float, float])

node_id: str
position: Tuple[float, float, float] = (0.0, 0.0, 0.0)
transceiver_properties: TransceiverProperties
update_mobility(timestamp)[source]
Return type:

None

Parameters:

timestamp (float)

pytwinnet.core.list_gnbs(network)[source]
Return type:

List[str]

Parameters:

network (Network)

Nodes

pytwinnet.core.node.Node

alias of WirelessNode

class pytwinnet.core.node.TransceiverProperties(transmit_power_dbm=20.0, antenna_gain_dbi=0.0, carrier_frequency_hz=2400000000.0, additional=<factory>)[source]

Bases: object

Parameters:
additional: Dict[str, Any]
antenna_gain_dbi: float = 0.0
carrier_frequency_hz: float = 2400000000.0
transmit_power_dbm: float = 20.0
class pytwinnet.core.node.WirelessNode(node_id, position=(0.0, 0.0, 0.0), transceiver_properties=<factory>, mobility_model=None, metadata=<factory>)[source]

Bases: object

Parameters:
metadata: Dict[str, Any]
mobility_model: Optional[object] = None
move_to(new_position)[source]
Return type:

None

Parameters:

new_position (Tuple[float, float, float])

node_id: str
position: Tuple[float, float, float] = (0.0, 0.0, 0.0)
transceiver_properties: TransceiverProperties
update_mobility(timestamp)[source]
Return type:

None

Parameters:

timestamp (float)

Network

class pytwinnet.core.network.Network(nodes=<factory>)[source]

Bases: object

Parameters:

nodes (Dict[str, WirelessNode])

add_node(node)[source]
Return type:

None

Parameters:

node (WirelessNode)

get_node_by_id(node_id)[source]
Return type:

Optional[WirelessNode]

Parameters:

node_id (str)

list_nodes()[source]
Return type:

List[WirelessNode]

nodes: Dict[str, WirelessNode]
remove_node(node_id)[source]
Return type:

None

Parameters:

node_id (str)

Digital Twin

class pytwinnet.core.digital_twin.DigitalTwin(network=<factory>, environment=None, propagation_model=None)[source]

Bases: object

Parameters:
environment: Optional[Environment] = None
network: Network
propagation_model: Optional[PropagationModel] = None
set_environment(env)[source]
Return type:

None

Parameters:

env (Environment)

set_propagation_model(model)[source]
Return type:

None

Parameters:

model (PropagationModel)

snapshot()[source]
Return type:

DigitalTwin

Ingestion

class pytwinnet.ingestion.DataSource[source]

Bases: ABC

abstractmethod connect()[source]
Return type:

None

abstractmethod read_data()[source]
Return type:

Iterable[object]

class pytwinnet.ingestion.MockDataSource(node_ids, step_size_m=1.0, seed=0)[source]

Bases: DataSource

Parameters:
connect()[source]
Return type:

None

read_data()[source]
Return type:

Iterable[MockNodeUpdate]

class pytwinnet.ingestion.MockNodeUpdate(node_id, new_position)[source]

Bases: object

Parameters:
new_position: Tuple[float, float, float]
node_id: str
class pytwinnet.ingestion.RealTimeMonitor(twin, source, on_applied=None)[source]

Bases: object

Parameters:
on_applied: Optional[Callable[[object], None]] = None
poll_once(updates=None)[source]
Return type:

int

Parameters:

updates (Iterable[object] | None)

source: DataSource
twin: DigitalTwin

Physics

class pytwinnet.physics.Environment(dimensions_m=(100.0, 100.0, 30.0), obstacles=<factory>)[source]

Bases: object

Parameters:
dimensions_m: Tuple[float, float, float] = (100.0, 100.0, 30.0)
is_within_bounds(position)[source]
Return type:

bool

Parameters:

position (Tuple[float, float, float])

obstacles: List[object]
class pytwinnet.physics.FadedModel(base, kind='rayleigh', K_dB=6.0, seed=0, epoch=None)[source]

Bases: PropagationModel

Wraps a base model and adds small-scale fading (Rayleigh or Rician) as an extra dB term: PL_faded = PL + fading_loss_db where fading_loss_db = -10*log10(|h|^2). Fading is deterministic per (tx, rx, epoch). Change ‘epoch’ to re-sample.

Parameters:
K_dB: float = 6.0
base: PropagationModel
calculate_path_loss(tx, rx, environment)[source]
Return type:

float

Parameters:
epoch: Optional[int] = None
kind: str = 'rayleigh'
seed: int = 0
set_epoch(epoch)[source]
Return type:

None

Parameters:

epoch (int | None)

class pytwinnet.physics.FreeSpacePathLoss[source]

Bases: PropagationModel

calculate_path_loss(tx, rx, environment)[source]
Return type:

float

Parameters:
faded_shadowed_model(freq_ghz, tx_power_dbm, n=2.0, sigma=6.0)[source]

Log-distance with lognormal shadowing + Rayleigh fading; returns Rx power (dBm).

Return type:

float

Parameters:
class pytwinnet.physics.PropagationModel[source]

Bases: ABC

abstractmethod calculate_path_loss(tx, rx, environment)[source]
Return type:

float

Parameters:
class pytwinnet.physics.RISAugmentedModel(base, ris, extra_loss_db=3.0)[source]

Bases: PropagationModel

Wrap a base PropagationModel and return the min of: - direct path loss - two-hop path loss via RIS: PL(tx->RIS) + PL(RIS->rx) - RIS_gain + extra_loss

Parameters:
calculate_path_loss(tx, rx, environment)[source]
Return type:

float

Parameters:
class pytwinnet.physics.RISBeamModel(base, ris, extra_loss_db=3.0)[source]

Bases: PropagationModel

Wraps a base PropagationModel. For a configured target UE (by id), the RIS contributes mainlobe gain on the two-hop path; others see sidelobe gain. Effective path loss = min( direct, (tx->RIS + RIS->rx - gain + extra_loss_db) ).

Parameters:
calculate_path_loss(tx, rx, environment)[source]
Return type:

float

Parameters:
set_beam(rx_node_id)[source]

Point mainlobe toward rx_node_id (or None for no specific target).

Return type:

None

Parameters:

rx_node_id (str | None)

class pytwinnet.physics.RISPanel(position, gain_db=10.0)[source]

Bases: object

Parameters:
gain_db: float = 10.0
position: Tuple[float, float, float]
class pytwinnet.physics.ShadowedModel(base, sigma_db=6.0, seed=0, epoch=None)[source]

Bases: PropagationModel

Wraps a base propagation model and adds log-normal shadowing (Gaussian in dB). Shadowing is deterministic per (tx, rx, epoch) for reproducibility. Change ‘epoch’ (int) to refresh the samples (e.g., per-drop or per-time-slot).

Parameters:
base: PropagationModel
calculate_path_loss(tx, rx, environment)[source]
Return type:

float

Parameters:
epoch: Optional[int] = None
seed: int = 0
set_epoch(epoch)[source]
Return type:

None

Parameters:

epoch (int | None)

sigma_db: float = 6.0
class pytwinnet.physics.SmartRISPanel(position, element_count=64, mainlobe_gain_db=None, sidelobe_gain_db=None)[source]

Bases: object

Toy RIS with a steerable mainlobe. Approximates array gain:

mainlobe_gain_db ~= 20*log10(N) sidelobe_gain_db ~= mainlobe - 13 dB (typical)

Parameters:
element_count: int = 64
mainlobe_gain_db: Optional[float] = None
position: Tuple[float, float, float]
sidelobe_gain_db: Optional[float] = None
pytwinnet.physics.calculate_noise_power(bw_hz, noise_figure_db=7.0)[source]
Return type:

float

Parameters:
pytwinnet.physics.calculate_sinr_db(signal_dbm, interference_dbm_list, noise_dbm)[source]
Return type:

float

Parameters:
pytwinnet.physics.noise_power_dbm(temperature_k, bandwidth_hz, noise_figure_db=0.0)[source]
Return type:

float

Parameters:
pytwinnet.physics.rx_power_dbm(tx_power_dbm, tx_gain_dbi, rx_gain_dbi, path_loss_db)[source]
Return type:

float

Parameters:
pytwinnet.physics.shannon_capacity(sinr_db, bw_hz, efficiency=0.75)[source]
Return type:

float

Parameters:
pytwinnet.physics.shannon_throughput_bps(bandwidth_hz, sinr_db_value, efficiency=1.0)[source]
Return type:

float

Parameters:
pytwinnet.physics.sinr_db(signal_dbm, interferers_dbm=None, noise_dbm=-100.0)[source]
Return type:

float

Parameters:

Simulation

class pytwinnet.simulation.Event(timestamp)[source]

Bases: object

Parameters:

timestamp (float)

apply(twin)[source]
Return type:

None

Parameters:

twin (DigitalTwin)

timestamp: float
class pytwinnet.simulation.MoveNodeEvent(timestamp, node_id, new_position)[source]

Bases: Event

Parameters:
apply(twin)[source]
Return type:

None

Parameters:

twin (DigitalTwin)

new_position: Tuple[float, float, float]
node_id: str
class pytwinnet.simulation.Scenario(duration_s, events=<factory>)[source]

Bases: object

Parameters:
add_event(event)[source]
Return type:

None

Parameters:

event (Event)

duration_s: float
events: List[Event]
sorted_events()[source]
Return type:

List[Event]

class pytwinnet.simulation.Simulator(twin)[source]

Bases: object

Parameters:

twin (DigitalTwin)

run(scenario, copy_twin=True)[source]
Return type:

DigitalTwin

Parameters:
twin: DigitalTwin
class pytwinnet.simulation.TrafficGenerationEvent(timestamp, source_node, dest_node, data_rate_mbps)[source]

Bases: Event

Parameters:
apply(twin)[source]
Return type:

None

Parameters:

twin (DigitalTwin)

data_rate_mbps: float
dest_node: str
source_node: str
pytwinnet.simulation.what_if(twin, scenario, objective=None)[source]
Return type:

Dict[str, Any]

Parameters:

Optimization

class pytwinnet.optimization.GridSearchOptimizer(param_grid, copy_twin=True)[source]

Bases: Optimizer

Parameters:
copy_twin: bool = True
optimize(twin, objective)[source]
Return type:

Dict[str, Any]

Parameters:
param_grid: Dict[str, Iterable[float]]
class pytwinnet.optimization.MaximizeThroughput[source]

Bases: Objective

evaluate(twin)[source]
Return type:

float

Parameters:

twin (DigitalTwin)

class pytwinnet.optimization.MinimizePowerConsumption[source]

Bases: Objective

evaluate(twin)[source]
Return type:

float

Parameters:

twin (DigitalTwin)

class pytwinnet.optimization.Objective[source]

Bases: ABC

abstractmethod evaluate(twin)[source]
Return type:

float

Parameters:

twin (DigitalTwin)

class pytwinnet.optimization.Optimizer[source]

Bases: ABC

abstractmethod optimize(twin, objective)[source]
Return type:

Dict[str, Any]

Parameters:
class pytwinnet.optimization.RandomSearchOptimizer(ranges_dbm, samples=32, seed=0, copy_twin=True)[source]

Bases: Optimizer

Parameters:
copy_twin: bool = True
optimize(twin, objective)[source]
Return type:

Dict[str, Any]

Parameters:
ranges_dbm: Dict[str, Tuple[float, float]]
samples: int = 32
seed: int = 0
class pytwinnet.optimization.SimpleGreedyOptimizer(step_db=1.0, max_power_dbm=30.0, iterations=10)[source]

Bases: Optimizer

Parameters:
iterations: int = 10
max_power_dbm: float = 30.0
optimize(twin, objective)[source]
Return type:

Dict[str, Any]

Parameters:
step_db: float = 1.0
class pytwinnet.optimization.SumThroughputObjective(tx_id, efficiency=1.0)[source]

Bases: Objective

Parameters:
evaluate(twin)[source]
Return type:

float

Parameters:

twin (DigitalTwin)

Mobility

class pytwinnet.mobility.ConstantVelocity(velocity_mps=(0.0, 0.0, 0.0), start_time=0.0, _last_t=None)[source]

Bases: object

Parameters:
start_time: float = 0.0
update(node, timestamp)[source]
Return type:

None

Parameters:
velocity_mps: Tuple[float, float, float] = (0.0, 0.0, 0.0)
class pytwinnet.mobility.RandomWaypoint(env, speed_range_mps=(0.5, 1.5), pause_time_s=0.0, seed=0, _rng=None, _target=None, _speed=0.0, _paused_until=0.0, _last_t=None)[source]

Bases: object

Parameters:
env: Environment
pause_time_s: float = 0.0
seed: int = 0
speed_range_mps: Tuple[float, float] = (0.5, 1.5)
update(node, timestamp)[source]
Return type:

None

Parameters:

Visualization

pytwinnet.visualization.plot_topology(network, ax=None)[source]
Return type:

Axes

Parameters:
pytwinnet.visualization.sinr_heatmap_2d(twin, tx_id, interferer_ids=None, plane_z=1.5, xlim=(0.0, 100.0), ylim=(0.0, 100.0), resolution=100, bandwidth_hz=20000000.0, noise_figure_db=7.0, show=False)[source]
Parameters:

Traffic

class pytwinnet.traffic.PoissonTraffic(rate_lambda, seed=0)[source]

Bases: object

Parameters:
arrivals(duration_s)[source]
Return type:

List[float]

Parameters:

duration_s (float)

rate_lambda: float
seed: int = 0