Skip to the content.

PureEdgeSim

PureEdgeSim, the Java version Tests Test coverage GPL v3 license Python 3.10 to 3.13

PureEdgeSim is now available in Python. This is the same simulator in another language, not a redesign of it: the same discrete-event engine, the same task lifecycle, the same orchestration algorithms, energy models and mobility models, the same five settings files and the same 56-column CSV, so a study run here can be placed side by side with the same study run in PureEdgeSim (Java).

Install it with pip, import it as pureedgesim, and run it with the pureedgesim command; the classes, parameters and reported columns keep their PureEdgeSim names. The manual is at charafeddinemechalikh.github.io/PureEdgeSim_Python/docs.

The core needs the Python standard library only (Python 3.10 or later). The one optional extra is pytest, to run the test suite. The live Swing charts of the Java framework are not ported, so nothing here needs a plotting library; the output is a CSV you plot with your own tools.

Install

git clone https://github.com/CharafeddineMechalikh/PureEdgeSim_Python.git
cd PureEdgeSim_Python
pip install -e .            # the library and the "pureedgesim" command
pip install -e .[test]      # plus pytest, to run the test suite

Without installing, point PYTHONPATH at src:

PYTHONPATH=src python -m pureedgesim.cli models

Command line

pureedgesim run executes every scenario of a settings folder, the way the Java Simulation does, and writes the results under a timestamped output folder.

pureedgesim run --settings examples/default_settings --devices 50 --duration 10 \
              --architectures MIST_AND_EDGE,ALL,CLOUD_ONLY \
              --algorithms TRADE_OFF,ROUND_ROBIN --seed 7
Ran 6 scenario(s), results in output\2026-10-28_16-06-05
  MIST_AND_EDGE  TRADE_OFF        50 tasks=  5330 ok=  4056 avg_delay=0.1801 energy=31.2369
  MIST_AND_EDGE  ROUND_ROBIN      50 tasks=  5330 ok=  4087 avg_delay=0.2059 energy=31.0528
  ALL            TRADE_OFF        50 tasks=  5330 ok=  4015 avg_delay=0.2256 energy=31.5172
  ALL            ROUND_ROBIN      50 tasks=  5330 ok=  4185 avg_delay=0.2062 energy=30.9939
  CLOUD_ONLY     TRADE_OFF        50 tasks=  5330 ok=   630 avg_delay=0.1693 energy=36.4156
  CLOUD_ONLY     ROUND_ROBIN      50 tasks=  5330 ok=   630 avg_delay=0.1693 energy=36.4156

The two CLOUD_ONLY rows are not a coincidence: with a single cloud data center both algorithms place every task on the same node.

Option Meaning
--settings folder holding the five settings files (default settings)
--output where the results go (default the output_folder property)
--devices run only this edge devices count
--architectures comma separated, overrides the properties file
--algorithms comma separated, overrides the properties file
--duration simulated minutes
--seed random_seed, makes the run reproducible
--parallel one worker process per scenario

pureedgesim models [--verbose] lists the mobility models that a settings folder may name.

Library

Run a sweep, the settings folder deciding which scenarios are executed:

from pureedgesim import COLUMNS, Simulation

simulation = Simulation("examples/default_settings", "output")
simulation.load()
simulation.parameters.simulation_duration = 300        # five simulated minutes
simulation.parameters.min_number_of_devices = simulation.parameters.max_number_of_devices = 20
simulation.parameters.orchestration_architectures = ["ALL", "MIST_ONLY"]
simulation.parameters.random_seed = 7
for row in simulation.launch():
    print(row[COLUMNS[0]], row[COLUMNS[1]], row[COLUMNS[7]], row[COLUMNS[4]])

Or drive one scenario yourself, which is what you want when you plot the metrics of a single run:

from pureedgesim import Engine, Randomness, Scenario, SimulationManager
from pureedgesim import load_parameters, load_settings

Randomness.set_seed(42)
Randomness.begin_scenario(0)
parameters = load_parameters("examples/default_settings/simulation_parameters.properties")
parameters.simulation_duration = 600          # seconds, in simulated time
files = load_settings("examples/default_settings")

engine = Engine(parameters.simulation_duration)
manager = SimulationManager(engine, parameters, files, Scenario("ALL", "TRADE_OFF", 50))
manager.start_simulation()
engine.start()

print(manager.metrics.generated_tasks, manager.metrics.succeeded_tasks)
print(manager.metrics.cloud_tasks, manager.metrics.edge_tasks, manager.metrics.mist_tasks)

summarize(manager) turns that manager into the same result row the sweep writes.

Custom policies

Every runnable example has its own folder and README, indexed in examples/. Subclass Orchestrator and hand the class to Simulation.set_custom_orchestrator. examples/custom_orchestrator/ places every task on the node closest to its device instead of balancing the load:

TRADE_OFF (default)      tasks= 1035 ok=  751 avg_delay=0.2025   cloud= 124 edge= 463 mist= 448 energy=11.5627
nearest node             tasks= 1035 ok=  735 avg_delay=0.2037   cloud=   0 edge= 522 mist= 513 energy=11.4244

examples/distributions_and_mobility/ replaces the constants of the settings files by probability distributions and lets the devices move with the Gauss-Markov model:

constants, static      tasks= 1035 ok=  751 delay=0.2025   failed(delay)= 284 failed(mobility)=   0 energy=11.5627
distributions, mobile  tasks= 1006 ok=  792 delay=0.2132   failed(delay)= 214 failed(mobility)=   0 energy=10.6488

examples/graph_partitioning/ and examples/genetic_partitioning/ partition a service graph instead of treating one request as one task. Both run their own copy of the video analytics pipeline, seven subtasks in settings/applications.xml, and share the solvers in src/pureedgesim/partitioning.py:

Each orchestrator clears the application placement after its decision, so every subtask is routed on its own:

GraphPartitioningOrchestrator - Application 0 (Soft Real-time): 3 partitions, cut 4 edges (720000 bits per request)
  P0.0 ['detect'] (4000 MI)
  P0.1 ['classify'] (2500 MI)
  P0.2 ['capture', 'preprocess', 'track', 'aggregate', 'notify'] (1950 MI)
tasks= 9600 ok= 9600 avg_delay=0.024    cloud=3537 edge=4349 mist=1714 failed(dependency)=   0 energy=47.9631

GeneticOrchestrator - Application 0 (Soft Real-time): fitness 0.0768 (makespan 0.0697 s, transfers 0.0011 s, imbalance 0.6244)
  {'capture': 'Mist#65', 'preprocess': 'Mist#65', 'detect': 'Mist#64', 'classify': 'Mist#54', 'track': 'Edge#2', 'aggregate': 'Mist#54', 'notify': 'Mist#54'}
tasks= 9600 ok= 9600 avg_delay=0.0252   cloud= 987 edge=5852 mist=2761 failed(dependency)=   0 energy=47.8312

A task generator returns its whole task list before the first event is scheduled, so a scenario can read a recorded workload instead of sampling it. examples/trace_replay/ replays the 8819 LLM inference requests of a published production trace (the Azure dataset of the Splitwise paper, CC-BY-4.0) on a server sized to the mean demand of that hour, and compares the replay with random arrivals at the same mean rate and mean task sizes: the two runs report almost the same CPU utilization, 42.42 and 42.76 percent, while the recorded one spends 120213 s in queues against 231 s and meets its deadlines for 36.06 percent of its requests against 100 percent. Because the replay draws nothing, its numbers are the Java ones: the same 8819 requests and the same 3180 successes.

Settings folder

The five files are the PureEdgeSim ones, read by load_settings and load_parameters:

File Content
simulation_parameters.properties duration, network, energy, the scenario sweep
edge_devices.xml device types, hardware, battery, mobility
edge_datacenters.xml edge servers, their positions and MAN links
cloud.xml the cloud data center
applications.xml task rates, sizes, lengths, arrival patterns and service graphs

Any numeric element may carry a distribution instead of a value, with the same attributes as the Java framework:

<task_length distribution="lognormal" mean="5000" std="1500"/>
<request_size distribution="weibull" shape="1.5" scale="2000" min="100"/>
<arrival>poisson</arrival>

Supported names: constant, uniform, triangular, normal, lognormal, exponential, erlang, gamma, weibull, pareto, beta, bernoulli, binomial, geometric, poisson, categorical. Sizes are read in kilobytes and stored in bits, task lengths in million instructions.

Service graphs

An application can describe a multi-stage service with <subtask> elements instead of a single task:

<application name="Video analytics">
    <type>Soft Real-time</type><rate>10</rate><usage_percentage>100</usage_percentage>
    <latency>0.5</latency><container_size>1500</container_size>
    <request_size>1500</request_size><results_size>50</results_size>
    <task_length>5000</task_length>
    <subtask name="capture" task_length="500"/>
    <subtask name="detect" depends_on="capture"/>
    <subtask name="alert" depends_on="detect" request_size="10"/>
</application>

Each request produces one task per subtask. Only the roots are submitted at the request time; a subtask enters the simulation when every name in its depends_on attribute has returned its results. task_length, request_size, results_size, container_size, and latency override the application value for that subtask, and omitted attributes inherit it. Names must be unique, every dependency must exist, and the graph must be acyclic; a violation raises a ValueError naming the file, application, and subtask.

The graph carries its data and its deadline. The results_size of each parent is added to the request_size of its children, so the payload of a dependency travels over the network, and the application latency is drawn once per request and shared by every subtask as an end-to-end deadline: a result that returns at or after it fails even when the subtask met its own bound.

When a subtask fails, its whole subtree fails with FAILED_BECAUSE_DEPENDENCY_FAILED and is counted in the “Tasks not executed because a dependency failed” column. A subtask marked optional="true" is skipped instead, with status SKIPPED: its dependents are released without the missing input and the request still counts as successful. An optional subtask must have at least one dependency, because an optional root could never be skipped. Applications without <subtask> elements produce exactly one independent task per request, as before.

The report measures requests as well as tasks: how many were generated, how many completed, and the average end-to-end makespan of the successful ones, in four columns appended at the end of the CSV row and in a Requests block of the text report.

The application placement is shared by default: the first subtask decides where the application runs, and the following subtasks reuse that destination. An orchestrator that routes subtasks to different nodes clears the placement after each decision with task.source_device.application_placed = False. The two partitioning examples in Custom policies do that.

Mobility models are selected with the model attribute of the <mobility> element, and their options are extra attributes:

Model Options, with their default
random-waypoint, default none; speed and the pause and mobility durations come from the device element
random-walk none
gauss-markov alpha (0.7), std (30 degrees), speed_std (20% of the mean speed)
manhattan grid_size (100 m)
waypoint waypoints (required, x1,y1;x2,y2), loop (true)
circular radius (required, must be > 0), center_x and center_y (the map centre), clockwise (true)
levy-walk min_step (the step distance), max_step (the map diagonal), shape (1.5)
brownian std (the step distance)
group, rpgm group (“1”), anchor_speed (the device speed), anchor_pause (0)

Devices of the same group follow one anchor path. Any dotted class path naming a MobilityModel subclass also works.

Reproducibility

Without a seed, the port draws from one shared, unseeded generator (the Java framework uses a SecureRandom there), so no two runs are alike. Set random_seed in the properties file, or pass --seed, and every scenario gets its own reproducible stream: scenario i draws from seed mixed with i, so a sweep replays itself and parallel workers agree with the sequential path. Randomness.set_seed() and Randomness.begin_scenario() are the programmatic equivalents.

Output

Sequential_simulation.csv (or Parallel_simulation.csv) holds the 56 PureEdgeSim columns, in the Java order, plus a Sequential_simulation.txt summary per scenario when save_log_file=true. pureedgesim.runner.COLUMNS is that column list.

Every timestamped output folder also receives two provenance artifacts:

An unreadable or invalid settings file stops the run with a message naming the file, the element, and the cause; nothing is silently skipped.

Tests

pip install -e .[test]
pytest              # 509 tests, the core, the settings readers, the CLI, and the examples

The integration tests build real managers and run them to the end, so the counters they check are the ones the simulation actually produced.

Citation

If you use PureEdgeSim in your research, please cite the following papers. Kindly cite the papers rather than the GitHub repository. This Python version is the same simulator, so it is cited with the same papers; add a link to the Python repository when you report which implementation you ran.

Mechalikh, C., Taktak, H., & Moussa, F. (2021). PureEdgeSim: A simulation framework for performance evaluation of cloud, edge and mist computing environments. Computer Science and Information Systems, 18(1), 43-66. https://doi.org/10.2298/CSIS200301042M

Mechalikh, C., Silem, A. E. H., Safavifar, Z., & Golpayegani, F. (2025). Quality matters: A comprehensive comparative study of edge computing simulators. Simulation Modelling Practice and Theory, 138, 103042. https://doi.org/10.1016/j.simpat.2024.103042

BibTeX:

@article{mechalikh2021pureedgesim,
  title   = {PureEdgeSim: A simulation framework for performance evaluation of cloud, edge and mist computing environments},
  author  = {Mechalikh, Charafeddine and Taktak, Hajer and Moussa, Faouzi},
  journal = {Computer Science and Information Systems},
  volume  = {18},
  number  = {1},
  pages   = {43--66},
  year    = {2021},
  doi     = {10.2298/CSIS200301042M}
}

@article{mechalikh2025quality,
  title   = {Quality matters: A comprehensive comparative study of edge computing simulators},
  author  = {Mechalikh, Charafeddine and Silem, Abd El Heq and Safavifar, Zahra and Golpayegani, Fatemeh},
  journal = {Simulation Modelling Practice and Theory},
  volume  = {138},
  pages   = {103042},
  year    = {2025},
  doi     = {10.1016/j.simpat.2024.103042}
}

See also

License

GPL-3.0-or-later, the same license as PureEdgeSim (see LICENSE).