Engee documentation
Notebook

Discrete event model of a Wi-Fi network

The model simulates the operation of a router with a limited number of parallel channels and several client devices that generate network traffic. The simulator demonstrates the fundamental principles of wireless networks, including competition for resources, queuing, and channel loading dynamics
.\ The main elements of our model are described below. Structure Событие It is a fundamental element of the discrete event model and represents a separate incident in the system that occurs at a specific time. It stores four main attributes. Field время It contains a timestamp indicating when an event should occur in conditional simulation units, which allows the scheduler to arrange events in chronological order. Field тип defines the event category using a Symbol, such as :генерация_запроса, :запрос or :конец_передачи, which dictates which processing function should be called. Field устройство binds an event to its source or recipient by storing the string identifier of a specific client device (for example, "Laptop_1"). Finally, the field данные It is intended for storing any additional information related to an event in the form of a tuple of named fields or another type; for example, for a request event, a unique request identifier and the time of its creation can be stored here. This structure is immutable, which guarantees the integrity of the event data after its creation throughout the simulation.

Structure Устройство simulates client hardware on a Wi-Fi network, such as a laptop or smartphone. It is mutable, since its state must be updated during the simulation. The structure includes three fields. Field имя It contains a unique string identifier of the device, for example "Smartphone_2", which is used to associate events and requests with their source. Field интенсивность determines the behavior of the device by storing the average time (in conditional simulation units) between the generation of consecutive data requests; the lower this number, the more often the device creates a load on the network. Field счетчик_запросов It tracks the total number of requests generated by this device since the beginning of the simulation; this value is incremented with each new request and is often used to generate unique identifiers for these requests.

Structure Роутер represents a Wi-Fi hotspot model with limited resources. This is a changeable structure, as the status of channels and queues is constantly changing. It includes three fields. Field каналы defines the maximum bandwidth of the router by storing an integer that indicates the number of parallel connections or channels available for simultaneous data transmission (for example, 2). The field занятые_каналы It tracks the current load, showing how many of the available channels are currently busy processing data transmission. Field очередь it is a vector of strings (Vector{String}), where each string is the identifier of a request (for example, "Laptop_1-03") from a device that tried to start transmitting, but all channels were busy; this vector implements FIFO (First—In, First-Out) logic, where the first The first incoming request will be served in the queue. The structure has a constructor Роутер(каналы::Int) which, when creating a new object, automatically initializes the number of occupied channels to zero and creates an empty queue, ensuring the correct initial state of the model.

Structure ПланировщикСобытий it is the core of the discrete event simulation engine, responsible for managing the time and sequence of all operations. This is a mutable structure. It contains two fields. Field события It is a chronologically ordered collection of all scheduled but not yet processed events, stored as a vector of structures. Событие. To efficiently extract the next event in time, the vector is maintained in a time-sorted state, which allows you to quickly extract the earliest event from the end of the vector. Field текущее_время stores the virtual time point to which the simulation has progressed; this value is updated each time a new event is retrieved and processed, and serves as a global clock for the entire model. The structure has a constructor ПланировщикСобытий(), which initializes an empty list of events and sets the initial simulation time to 0.0. The main simulation loop constantly requests the scheduler for the next event, updates текущее_время at the time of this event, and transmits the event for processing to the appropriate functions, thereby simulating the passage of time in the system.

In [ ]:
struct Event
    Time::Float64
    Type::Symbol
    Device::String
    data::Any
end

mutable struct Device
    name::String
    intensity::Float64
    counter_query::Int
end

mutable struct Router
    channels::Int
    Busy channels::Int
    queue::Vector{String}
    
    function Router(channels::Int)
        new(channels, 0, String[])
    end
end

mutable structure of event schedulers
    events::Vector{Event}
    current_time::Float64
    
    Event Scheduler function()
        new(Event[], 0.0)
    end
end

Function добавить_событие! places a new event in the scheduler and sorts the entire list by time in reverse order, ensuring that the earliest event is always at the end of the vector. Function следующее_событие retrieves and returns this earliest event from the end of the vector, and if the list is empty, returns nothing, which signals the end of the simulation.

In [ ]:
function_include an event!(planner::Event planner, event::Event)
    push!(the planner.events, events)
    sort!(the planner.events, by=x->x.time, rev=true)
end

next event function(scheduler::Event Planner)
    isempty(scheduler.events) ? nothing : pop!(the planner.events)
end

Function генерировать_запрос! creates a new network request from the device: it increments the request counter, generates a unique identifier, and immediately schedules an event like :запрос for the current time, and then calculates a random interval (based on the characteristic интенсивность devices) for planning the next event :генерация_запроса, thereby simulating the periodic activity of the device.

Function обработать_запрос receives a data transfer request and tries to allocate a router resource for it. If there is a free channel, it occupies it, calculates a random transmission duration, and schedules an event. :конец_передачи for the future, and displays a success message. If all channels are busy, the function places the request ID in the router queue and displays a message about the standby status.

Function завершить_передачу releases the router channel after the data transfer is completed, displaying the corresponding message. Then it checks for pending requests in the queue: if the queue is not empty, the function retrieves the next request, immediately allocates the newly released channel for it, schedules its completion and outputs a message, thus implementing continuous processing and the FIFO principle (First-In, First-Out).

In [ ]:
function_ generate a query!(device::Device, scheduler::Event SchedulerefErences, 
                              Current time::Float64, router::The router)
    device.counter_query += 1
    запрос_ид = "$(device.name)-$(lpad(device.counter_query, 2, '0'))"
    
    event_query = Event(
        current_time,
        :request,
        device.name,
        (id=request_id, time_query=current time)
    )
    
    add_ event!(scheduler, event_query)
    
    next time_ = current time_ + device.intensity * (0.5 + rand())
    next event = Event(
        next_time,
        :generation_query,
        device.name,
        nothing
    )
    
    add_ event!(scheduler, next event)
    
    println(" t=$(round(current time, digits=2)) | $(device.name) | Request created $request_id")
end

function process_query(device::Device, Data, Scheduler::Event SchedulerefErences, 
                          the router::Router, current time::Float64)
    request_id = data.identifier
    query time_ = data.time_query
    
    if the router.busy channels < router.channels
        the router.busy channels += 1
        
        transmission time_ = 0.5 + rand() * 2.0
        end time = current time + transfer time
        
        event = Event(
            end time_,
            :end_transmission,
            device.name,
            (id=request_id, start time=current time, 
             transfer time_=transfer time_)
        )
        
        add_ event!(planner, event)
        
        println(" t=$(round(current time, digits=2)) | $request_id | Channel received (waiting: $(round(current time is the time of the request, digits=2)))")
    else
        push!(the router.queue, request_id)
        println(" t=$(round(current time, digits=2)) | $request_id | Queued (length: $(length(router.queue)))")
    end
end

function end_transmission(device::Device, Data, Scheduler::Event SchedulerefErences, 
                          the router::Router, current time::Float64)
    the router.busy channels -= 1
    
    request_id = data.identifier
    start time_ = data.start time_
    time_transmission = data.time_transmissions
    
    println("t=$(round(current time, digits=2)) | $request_id | Channel is released (transmission: $(round(transmission time_, digits=2)))")
    
    if !isempty(router.queue)
        next_query = popfirst!(the router.queue)
        имя_устройства = split(следующий_запрос, "-")[1]
        
        transmission time_ = 0.5 + rand() * 2.0
        end time = current time + transfer time
        
        event = Event(
            end time_,
            :end_transmission,
            device_name,
            (id=next query, start time=current time,
             transfer time_=transfer time_)
        )
        
        add_ event!(planner, event)
        
        the router.busy channels += 1
        println("t=$(round(current time, digits=2)) | $next_query | Has been removed from the queue and transmission has begun")
    end
end

Function запустить_симуляцию He is the main managing director of the program. It initializes all system components: it creates an event scheduler, a router with a set number of channels, and a set number of client devices with random characteristics, after which it schedules the first request generation event for each of them.

Then the function starts the main cycle of discrete event simulation, which continuously extracts the earliest event from the scheduler, promotes the virtual time of the system and, depending on the type of event, redirects it to processing one of the three key functions.: генерировать_запрос!, обработать_запрос or завершить_передачу. The loop runs until the set simulation time expires or until the events run out, after which the function outputs the final network statistics and returns them as a dictionary.

In [ ]:
function_simulation is started(duration=30.0, number of devices=4, router channels=2)
    println("="^50)
    println("WI-FI NETWORK SIMULATION (PURE JULIA)")
    println("="^50)
    println("Parameters:")
    println("  Duration: $unit time duration")
    println("  Devices: $number of devices")
    println("  Router channels: $Router channels")
    println("-"^50)
    
    scheduler = Event Planner()
    router = Router(Router channels)
    
    device_dict = Dict{String, Device}()
    типы_устройств = ["A laptop", "Smartphone", "Tablet", "TV"]
    
    for i in 1:number of devices
        type = device type_[mod1(i, length(device type_))]
        имя = "$(type)_$i"
        intensity = 2.0 + rand() * 3.0
        Device = Device(name, intensity, 0)
        device_dict[name] = device
        println("➕ Added: $name (interval: $(round(intensity, digits=2)))")
        
        first_existence = Event(
            rand() * 1.0,
            :generation_query,
            name,
            nothing
        )
        add_ event!(planner, first_event)
    end
    
    println("-"^50)
    println("Running the simulation...\n")
    
    statistics = Dict{String, Int}()
    статистика["all_questions"] = 0
    статистика["transfer_completed"] = 0
    
    while the scheduler.current_time < duration
        event = next event(scheduler)
        
        if event === nothing
            break
        end
        
        the planner.current_time = event.time
        
        if the scheduler.current_time > duration
            break
        end
        
        if !haskey(device_dict, event.device)
            continue
        end
        
        device = device_dict[event.device]
        
        if event.type == :generation_query
            Generate a query!(device, scheduler, scheduler.current time, router)
            статистика["all_questions"] += 1
            
        elseif event.type == :request
            process_query(device, event.data, scheduler, router, scheduler.current_time)
            
        elseif event.type == :end_transmission
            end_transmission(device, event.data, scheduler, router, scheduler.current_time)
            статистика["transfer_completed"] += 1
        end
    end
    
    println("\n" * "-"^50)
    println("SIMULATION STATISTICS")
    println("-"^50)
    println("Total simulation time: $(round(scheduler.current_time, digits=2))")
    println("Total requests: $(statistics["всего_запросов"])")
    println("Transfers completed: $(statistics["передач_завершено"])")
    println("Remaining in the queue: $(length(router.queue))")
    println("Number of channels used at the end: $(router.busy channels)")
    println("="^50)
    
    return statistics
end
In [ ]:
run_simulation(25.0, 5, 2)
==================================================
СИМУЛЯЦИЯ WI-FI СЕТИ (ЧИСТЫЙ JULIA)
==================================================
Параметры:
  Длительность: 25.0 ед. времени
  Устройства: 5
  Каналы роутера: 2
--------------------------------------------------
➕ Добавлено: Ноутбук_1 (интервал: 2.53)
➕ Добавлено: Смартфон_2 (интервал: 4.12)
➕ Добавлено: Планшет_3 (интервал: 4.73)
➕ Добавлено: ТВ_4 (интервал: 3.34)
➕ Добавлено: Ноутбук_5 (интервал: 4.24)
--------------------------------------------------
Запуск симуляции...

⏰ t=0.03 | Ноутбук_1 | Создан запрос Ноутбук_1-01
✅ t=0.03 | Ноутбук_1-01 | Канал получен (ожидание: 0.0)
⏰ t=0.1 | Планшет_3 | Создан запрос Планшет_3-01
✅ t=0.1 | Планшет_3-01 | Канал получен (ожидание: 0.0)
⏰ t=0.14 | Ноутбук_5 | Создан запрос Ноутбук_5-01
⌛ t=0.14 | Ноутбук_5-01 | Поставлен в очередь (длина: 1)
⏰ t=0.36 | Смартфон_2 | Создан запрос Смартфон_2-01
⌛ t=0.36 | Смартфон_2-01 | Поставлен в очередь (длина: 2)
⏰ t=0.42 | ТВ_4 | Создан запрос ТВ_4-01
⌛ t=0.42 | ТВ_4-01 | Поставлен в очередь (длина: 3)
🔓 t=1.17 | Планшет_3-01 | Канал освобожден (передача: 1.07)
➡️  t=1.17 | Ноутбук_5-01 | Извлечен из очереди и начал передачу
🔓 t=2.32 | Ноутбук_1-01 | Канал освобожден (передача: 2.3)
➡️  t=2.32 | Смартфон_2-01 | Извлечен из очереди и начал передачу
⏰ t=2.68 | Смартфон_2 | Создан запрос Смартфон_2-02
⌛ t=2.68 | Смартфон_2-02 | Поставлен в очередь (длина: 2)
⏰ t=2.73 | ТВ_4 | Создан запрос ТВ_4-02
⌛ t=2.73 | ТВ_4-02 | Поставлен в очередь (длина: 3)
🔓 t=2.78 | Ноутбук_5-01 | Канал освобожден (передача: 1.6)
➡️  t=2.78 | ТВ_4-01 | Извлечен из очереди и начал передачу
⏰ t=2.79 | Планшет_3 | Создан запрос Планшет_3-02
⌛ t=2.79 | Планшет_3-02 | Поставлен в очередь (длина: 3)
🔓 t=3.29 | ТВ_4-01 | Канал освобожден (передача: 0.51)
➡️  t=3.29 | Смартфон_2-02 | Извлечен из очереди и начал передачу
⏰ t=3.67 | Ноутбук_1 | Создан запрос Ноутбук_1-02
⌛ t=3.67 | Ноутбук_1-02 | Поставлен в очередь (длина: 3)
🔓 t=4.79 | Смартфон_2-01 | Канал освобожден (передача: 2.46)
➡️  t=4.79 | ТВ_4-02 | Извлечен из очереди и начал передачу
🔓 t=5.36 | Смартфон_2-02 | Канал освобожден (передача: 2.07)
➡️  t=5.36 | Планшет_3-02 | Извлечен из очереди и начал передачу
🔓 t=5.71 | ТВ_4-02 | Канал освобожден (передача: 0.93)
➡️  t=5.71 | Ноутбук_1-02 | Извлечен из очереди и начал передачу
⏰ t=5.78 | Ноутбук_5 | Создан запрос Ноутбук_5-02
⌛ t=5.78 | Ноутбук_5-02 | Поставлен в очередь (длина: 1)
⏰ t=6.02 | ТВ_4 | Создан запрос ТВ_4-03
⌛ t=6.02 | ТВ_4-03 | Поставлен в очередь (длина: 2)
🔓 t=6.34 | Ноутбук_1-02 | Канал освобожден (передача: 0.62)
➡️  t=6.34 | Ноутбук_5-02 | Извлечен из очереди и начал передачу
⏰ t=6.73 | Ноутбук_1 | Создан запрос Ноутбук_1-03
⌛ t=6.73 | Ноутбук_1-03 | Поставлен в очередь (длина: 2)
🔓 t=7.41 | Планшет_3-02 | Канал освобожден (передача: 2.05)
➡️  t=7.41 | ТВ_4-03 | Извлечен из очереди и начал передачу
⏰ t=7.81 | ТВ_4 | Создан запрос ТВ_4-04
⌛ t=7.81 | ТВ_4-04 | Поставлен в очередь (длина: 2)
🔓 t=8.0 | Ноутбук_5-02 | Канал освобожден (передача: 1.67)
➡️  t=8.0 | Ноутбук_1-03 | Извлечен из очереди и начал передачу
⏰ t=8.34 | Смартфон_2 | Создан запрос Смартфон_2-03
⌛ t=8.34 | Смартфон_2-03 | Поставлен в очередь (длина: 2)
⏰ t=8.77 | Ноутбук_1 | Создан запрос Ноутбук_1-04
⌛ t=8.77 | Ноутбук_1-04 | Поставлен в очередь (длина: 3)
⏰ t=8.82 | Планшет_3 | Создан запрос Планшет_3-03
⌛ t=8.82 | Планшет_3-03 | Поставлен в очередь (длина: 4)
🔓 t=9.03 | Ноутбук_1-03 | Канал освобожден (передача: 1.03)
➡️  t=9.03 | ТВ_4-04 | Извлечен из очереди и начал передачу
🔓 t=9.18 | ТВ_4-03 | Канал освобожден (передача: 1.77)
➡️  t=9.18 | Смартфон_2-03 | Извлечен из очереди и начал передачу
⏰ t=9.24 | Ноутбук_5 | Создан запрос Ноутбук_5-03
⌛ t=9.24 | Ноутбук_5-03 | Поставлен в очередь (длина: 3)
🔓 t=9.54 | ТВ_4-04 | Канал освобожден (передача: 0.51)
➡️  t=9.54 | Ноутбук_1-04 | Извлечен из очереди и начал передачу
⏰ t=10.5 | Смартфон_2 | Создан запрос Смартфон_2-04
⌛ t=10.5 | Смартфон_2-04 | Поставлен в очередь (длина: 3)
🔓 t=10.93 | Ноутбук_1-04 | Канал освобожден (передача: 1.39)
➡️  t=10.93 | Планшет_3-03 | Извлечен из очереди и начал передачу
🔓 t=11.14 | Смартфон_2-03 | Канал освобожден (передача: 1.96)
➡️  t=11.14 | Ноутбук_5-03 | Извлечен из очереди и начал передачу
⏰ t=11.31 | ТВ_4 | Создан запрос ТВ_4-05
⌛ t=11.31 | ТВ_4-05 | Поставлен в очередь (длина: 2)
🔓 t=11.85 | Ноутбук_5-03 | Канал освобожден (передача: 0.71)
➡️  t=11.85 | Смартфон_2-04 | Извлечен из очереди и начал передачу
⏰ t=12.11 | Ноутбук_1 | Создан запрос Ноутбук_1-05
⌛ t=12.11 | Ноутбук_1-05 | Поставлен в очередь (длина: 2)
🔓 t=12.88 | Планшет_3-03 | Канал освобожден (передача: 1.95)
➡️  t=12.88 | ТВ_4-05 | Извлечен из очереди и начал передачу
🔓 t=13.06 | Смартфон_2-04 | Канал освобожден (передача: 1.21)
➡️  t=13.06 | Ноутбук_1-05 | Извлечен из очереди и начал передачу
🔓 t=13.46 | ТВ_4-05 | Канал освобожден (передача: 0.57)
⏰ t=13.68 | Планшет_3 | Создан запрос Планшет_3-04
✅ t=13.68 | Планшет_3-04 | Канал получен (ожидание: 0.0)
🔓 t=13.86 | Ноутбук_1-05 | Канал освобожден (передача: 0.8)
⏰ t=14.62 | Ноутбук_5 | Создан запрос Ноутбук_5-04
✅ t=14.62 | Ноутбук_5-04 | Канал получен (ожидание: 0.0)
🔓 t=14.69 | Планшет_3-04 | Канал освобожден (передача: 1.01)
⏰ t=15.36 | ТВ_4 | Создан запрос ТВ_4-06
✅ t=15.36 | ТВ_4-06 | Канал получен (ожидание: 0.0)
⏰ t=15.66 | Ноутбук_1 | Создан запрос Ноутбук_1-06
⌛ t=15.66 | Ноутбук_1-06 | Поставлен в очередь (длина: 1)
⏰ t=15.99 | Смартфон_2 | Создан запрос Смартфон_2-05
⌛ t=15.99 | Смартфон_2-05 | Поставлен в очередь (длина: 2)
🔓 t=16.43 | ТВ_4-06 | Канал освобожден (передача: 1.07)
➡️  t=16.43 | Ноутбук_1-06 | Извлечен из очереди и начал передачу
🔓 t=16.48 | Ноутбук_5-04 | Канал освобожден (передача: 1.86)
➡️  t=16.48 | Смартфон_2-05 | Извлечен из очереди и начал передачу
🔓 t=17.32 | Ноутбук_1-06 | Канал освобожден (передача: 0.89)
⏰ t=17.43 | Планшет_3 | Создан запрос Планшет_3-05
✅ t=17.43 | Планшет_3-05 | Канал получен (ожидание: 0.0)
⏰ t=17.57 | Ноутбук_1 | Создан запрос Ноутбук_1-07
⌛ t=17.57 | Ноутбук_1-07 | Поставлен в очередь (длина: 1)
🔓 t=18.84 | Смартфон_2-05 | Канал освобожден (передача: 2.36)
➡️  t=18.84 | Ноутбук_1-07 | Извлечен из очереди и начал передачу
🔓 t=19.02 | Планшет_3-05 | Канал освобожден (передача: 1.59)
⏰ t=19.06 | Ноутбук_5 | Создан запрос Ноутбук_5-05
✅ t=19.06 | Ноутбук_5-05 | Канал получен (ожидание: 0.0)
⏰ t=20.06 | ТВ_4 | Создан запрос ТВ_4-07
⌛ t=20.06 | ТВ_4-07 | Поставлен в очередь (длина: 1)
⏰ t=20.28 | Смартфон_2 | Создан запрос Смартфон_2-06
⌛ t=20.28 | Смартфон_2-06 | Поставлен в очередь (длина: 2)
🔓 t=20.29 | Ноутбук_5-05 | Канал освобожден (передача: 1.23)
➡️  t=20.29 | ТВ_4-07 | Извлечен из очереди и начал передачу
⏰ t=20.38 | Ноутбук_1 | Создан запрос Ноутбук_1-08
⌛ t=20.38 | Ноутбук_1-08 | Поставлен в очередь (длина: 2)
🔓 t=20.88 | Ноутбук_1-07 | Канал освобожден (передача: 2.04)
➡️  t=20.88 | Смартфон_2-06 | Извлечен из очереди и начал передачу
⏰ t=21.66 | Планшет_3 | Создан запрос Планшет_3-06
⌛ t=21.66 | Планшет_3-06 | Поставлен в очередь (длина: 2)
🔓 t=21.85 | ТВ_4-07 | Канал освобожден (передача: 1.55)
➡️  t=21.85 | Ноутбук_1-08 | Извлечен из очереди и начал передачу
⏰ t=22.03 | Ноутбук_5 | Создан запрос Ноутбук_5-06
⌛ t=22.03 | Ноутбук_5-06 | Поставлен в очередь (длина: 2)
⏰ t=22.45 | Ноутбук_1 | Создан запрос Ноутбук_1-09
⌛ t=22.45 | Ноутбук_1-09 | Поставлен в очередь (длина: 3)
⏰ t=22.6 | ТВ_4 | Создан запрос ТВ_4-08
⌛ t=22.6 | ТВ_4-08 | Поставлен в очередь (длина: 4)
🔓 t=22.74 | Смартфон_2-06 | Канал освобожден (передача: 1.86)
➡️  t=22.74 | Планшет_3-06 | Извлечен из очереди и начал передачу
🔓 t=23.3 | Планшет_3-06 | Канал освобожден (передача: 0.56)
➡️  t=23.3 | Ноутбук_5-06 | Извлечен из очереди и начал передачу
🔓 t=23.72 | Ноутбук_1-08 | Канал освобожден (передача: 1.88)
➡️  t=23.72 | Ноутбук_1-09 | Извлечен из очереди и начал передачу
⏰ t=24.47 | ТВ_4 | Создан запрос ТВ_4-09
⌛ t=24.47 | ТВ_4-09 | Поставлен в очередь (длина: 2)
🔓 t=24.52 | Ноутбук_5-06 | Канал освобожден (передача: 1.22)
➡️  t=24.52 | ТВ_4-08 | Извлечен из очереди и начал передачу
⏰ t=24.52 | Смартфон_2 | Создан запрос Смартфон_2-07
⌛ t=24.52 | Смартфон_2-07 | Поставлен в очередь (длина: 2)

--------------------------------------------------
СТАТИСТИКА СИМУЛЯЦИИ
--------------------------------------------------
Общее время симуляции: 25.48
Всего запросов: 37
Передач завершено: 33
Осталось в очереди: 2
Занято каналов в конце: 2
==================================================
Out[0]:
Dict{String, Int64} with 2 entries:
  "передач_завершено" => 33
  "всего_запросов"    => 37

The simulation demonstrates the operation of the network in a state of periodic overload: with five active devices and only two router channels, a waiting queue is constantly forming in the system, the peak length of which can reach several requests, which indicates a systematic lack of bandwidth for the current load. There is a clear division of operating time into phases: in the initial period, when the queue has not yet been formed, requests are served almost instantly, but as the load accumulates, most devices are forced to spend considerable time waiting for a free channel, and some of them, generating requests with higher intensity, create a disproportionately large load, increasing the average waiting time. for all members of the network. Although the system as a whole copes with processing the vast majority of requests, the presence of a queue by the end of the session and the constant employment of all channels indicate that it is working at the limit of its capabilities, where any increase in the intensity of requests or the number of devices will lead to a sharp increase in delays and a decrease in overall efficiency.

Conclusion

This implementation demonstrates how a fully functional discrete event model can be built on pure Julia without external dependencies. This code provides an excellent basis for experimenting with network algorithms. You can easily modify it to explore different scenarios: add different service classes, device priorities, packet loss models, or real-time visualization.

The main advantage of this implementation is full control over the simulation logic and the absence of dependencies, which makes the code ideal for training and prototyping network models.