Simulation models
The simulator replaces every physical subsystem with a small, deterministic model. This page documents the four that produce the reported numbers: energy, mobility, distributions, and network. Each is a set of plain Python classes you can read end to end.
Energy model
EnergyModelComputingNode (pureedgesim/nodes.py) is a linear power model with a battery option. The fields are idle_consumption, max_consumption, battery_powered, battery_capacity, initial_battery_level, plus accumulated cpu_energy_consumption and network_energy_consumption.
CPU energy has two parts:
- Static:
add_static_energy(seconds)addsidle_consumption / 3600 * seconds. It is charged everyupdate_intervalon each alive node, inComputingNode.update_status. - Dynamic:
add_dynamic_energy(task_length, total_mips)adds(max_consumption - idle_consumption) / 3600 * task_length / total_mips, charged when a task starts execution.
Network energy is charged per transfer through update_wireless_energy_consumption(size_in_bits, flag) for edge devices, using the transmission or reception energy-per-bit set by set_connectivity_type. get_total_energy_consumption() returns cpu_energy_consumption + network_energy_consumption, all in Watt-hours.
Battery accounting: a battery node whose battery_capacity - get_total_energy_consumption() drops to zero or below dies at the next status tick (update_status sets NodeStatus.DEAD and death_time, and increments dead_devices). When a device type has battery true and an initialBatteryLevel of, say, 50, the topology generator seeds cpu_energy_consumption to capacity * (1 - level/100) so the fleet starts half charged.
Energy-per-bit defaults come from the parameters (see Configuration); Wi-Fi, cellular, and Ethernet each have their own transmission and reception figures.
Probability distributions
Sixteen distribution classes live in pureedgesim/distribution.py, all subclassing Distribution. A distribution draws lazily from the shared Randomness stream, resolved per draw rather than at construction, so a distribution parsed before the seed is set still honors the seed at run time.
The abstract interface is sample() (a float draw), mean(), min/max properties, and sample_int() (floor of sample()). Discrete families set is_discrete = True.
Configure any numeric XML field with a distribution element:
<task_length distribution="lognormal" mean="25000" std="8000" min="5000"/>
Or from Python:
from pureedgesim import NormalDistribution, ExponentialDistribution
length = NormalDistribution(25000, 8000).bounded(5000, float("inf"))
gap = ExponentialDistribution(3.0)
print(length.sample(), gap.mean())
| Name | Required options | Notes |
|---|---|---|
constant |
value |
Always returns value. |
uniform |
min, max |
Needs max >= min; already bounded, ignores extra min/max. |
triangular |
min, mode, max |
Needs min <= mode <= max. |
normal |
mean, std |
Unbounded; pair with min/max to clamp. |
lognormal |
mean, std |
mean/std are of the value, not of its logarithm. |
exponential |
mean |
Mean > 0. |
erlang |
shape, scale |
shape is a whole number >= 1; a GammaDistribution subclass. |
gamma |
shape, scale |
Both > 0. |
weibull |
shape, scale |
Both > 0. |
pareto |
scale, shape |
Both > 0; min == scale; mean is infinite when shape <= 1. |
beta |
alpha, beta |
Both > 0; support [0, 1]. |
bernoulli |
probability |
Returns 1.0 or 0.0; discrete. |
binomial |
trials, probability |
trials >= 0, 0 <= p <= 1; discrete. |
geometric |
probability |
Trials up to and including the first success, so support starts at 1; discrete. |
poisson |
mean |
Mean > 0; uses a normal approximation for large means; discrete. |
categorical |
values, optional weights |
Comma-separated; discrete when every value is a whole number. |
For any family except uniform and triangular, an optional min/max attribute wraps the draw in a BoundedDistribution (rejection sampling, clamped after 100 tries). A ScaledDistribution multiplies a draw by a factor; the XML parser uses it to convert the KB size fields into bits.
DistributionFactory.create(name, options, rng) builds a distribution from the attribute map; distribution_from_element(element) reads an XML element, treating a bare number as a constant.
Mobility models
Locations are a frozen Location(x_pos, y_pos) dataclass. The base MobilityModel (pureedgesim/mobility.py) does not move on events. generate_path() precomputes the whole trajectory once, keyed by millisecond ticks, and update_location(time) reads it. A model only needs to implement get_next_location(location).
The base class carries speed, min/max_pause_duration, min/max_mobility_duration, and a parameters dict of model options read from the <mobility> element’s attributes (case-insensitive). Helpers: get_parameter, get_parameter_int, get_parameter_bool, get_required_parameter. The map is [0, width] x [0, length]; models either reflect_location (bounce) or clamp_to_map.
MobilityModelFactory maps names to classes:
| Name | Class | Behavior and options |
|---|---|---|
random-waypoint, default |
DefaultMobilityModel |
Random waypoint: moves for a mobility duration, pauses, re-orients; reflects at the borders. |
random-walk |
RandomWalkMobilityModel |
Each step picks a random heading of length speed * update_interval. |
gauss-markov |
GaussMarkovMobilityModel |
Correlated velocity. Options: alpha (0 to 1, default 0.7), std (direction noise, default 30), speed_std (default 0.2 * mean_speed). |
manhattan |
ManhattanMobilityModel |
Axis-aligned movement between grid intersections. Option: grid_size (default 100). |
waypoint |
WaypointMobilityModel |
Follows a fixed list. Required option waypoints="x1,y1;x2,y2;..." (at least two points). Option loop (default true). |
circular |
CircularMobilityModel |
Orbits a center. Options: radius (required, > 0), center_x, center_y, clockwise (default true). |
levy-walk |
LevyWalkMobilityModel |
Heavy-tailed flight lengths (Pareto). Options: min_step, max_step, shape (default 1.5). |
brownian |
BrownianMobilityModel |
Gaussian jitter each step. Option: std (default speed * update_interval). |
group, rpgm |
ReferencePointGroupMobilityModel |
Points move with an offset from a randomly generated anchor path. Options: group (seed, default “1”), anchor_speed, anchor_pause. |
Two names alias to the same class (random-waypoint/default, and group/rpgm), which is why pureedgesim models lists eleven names for nine classes. You can also pass a dotted class path to the factory; it must resolve to a MobilityModel subclass.
A device with a mobility_model but no explicit <mobility model> (a plain <mobility>true</mobility>) uses manager.default_mobility_model, the class you can override with Simulation.set_custom_mobility_model(SomeModelClass).
Network model
DefaultNetworkModel (pureedgesim/network.py) routes, shares bandwidth, and charges link and device energy. Links are NetworkLink objects with bandwidth (bps), delay (s), energy_per_bit (Wh/bit), a LinkType (WAN, MAN, LAN, or IGNORE), and a LinkTechnology (WIFI, CELLULAR, ETHERNET) which is None on an infrastructure link, because no radio category counts it.
An IGNORE link routes traffic and delays the tasks that cross it, but appears in no usage or energy counter. The default topology uses one: the hop from the WAN router to the cloud. The device link that entered the WAN already booked those bits, and counting the shared last hop again would report the same traffic twice.
Bandwidth sharing is equal among concurrent transfers: allocated_bandwidth() is bandwidth / len(transfers). Every link is served by its own event: the static model wakes a link at the current clock and clears its whole payload in one step, so a hop never advances the clock by more than its propagation delay, exactly like the Java NetworkLink. realistic_network_model instead polls the link every network_update_interval and moves interval * allocated_bandwidth bits per tick, so progress is gradual and contention is visible. In both modes a transfer that joins a link whose tick is already pending waits for that tick, which is how two transfers sent at the same second end up sharing the capacity. When the last hop of a route is served, the arrival is scheduled TRANSFER_FINISHED one link latency later, and only then does the workflow carry on.
The default topology (topology.DefaultTopologyCreator) lays the links the router works on. Each device gets an access pair to its serving edge data center, at the radio bandwidth and latency, with an energy per bit that sums the two ends of the hop. It gets a second pair towards the WAN, that keeps the same radio technology and the same link class, runs at the slower of the radio and the WAN bandwidth, and adds the WAN energy per bit on top. Only the gateway data center, the edge data center named dc1 or the first one when the file uses other names, hangs on the WAN: the others reach the cloud hop by hop through the MAN ring. When a device moves, connect_with does not add links, it re-points the pair it already owns with InfrastructureGraph.move, so a long run with mobile devices keeps one access link per device instead of accumulating the history of its positions.
Routing is latency-weighted Dijkstra on InfrastructureGraph, with paths cached per source/destination pair. Edge devices are never used as intermediate hops (only as endpoints), so mobility cannot open shortcuts. route_for(source, destination) handles four cases:
flowchart TD
SEND["route_for(source, destination)"] --> SAME{"source is destination?"}
SAME -- "yes" --> EMPTY["No link, immediate"]
SAME -- "no" --> D2D{"Both edge devices?"}
D2D -- "yes" --> LOCAL["Device-to-device LAN link if within edge_devices_range"]
D2D -- "no" --> SRCDEV{"Source is an edge device?"}
SRCDEV -- "yes" --> UP["Access link to serving edge DC, else WAN link, then shortest path"]
SRCDEV -- "no" --> DSTDEV{"Destination is an edge device?"}
DSTDEV -- "yes" --> DOWN["Shortest path to serving DC or router, then access link down"]
DSTDEV -- "no" --> SP["Shortest path in the infrastructure graph"]
When a transfer finishes a hop, _charge books elapsed_time into network_usage (by link type) and technology_usage, records container traffic separately, adds the transferred bits to the link’s energy (transferred_bits * energy_per_bit), and charges the source (transmission) and destination (reception) energy when an endpoint is an edge device. An IGNORE hop still adds its seconds to the network time of the task that crossed it; only the usage, traffic and energy counters stay where they were. The accumulated per-type and per-technology totals become the network-usage and network-energy columns; see Output and metrics.
Cross-links
- Energy, mobility, and network in the Java manual (identical formulas): Java Simulation models.
- How to replace a model with your own: the mobility hook in Getting started and the network/customization gaps in Python vs Java.