
Выводы:
В ходе выполнения лабораторной работы были получены навыки моделирования стандартных сценариев работы телекоммуникационных систем с топологией типа «звезда», изучены свойства алгоритмов планирования ресурсов нисходящего кадра Maximum Throughput, Proportion Fair и Equal Blind в подобных системах. Изучены стратегии распределения ресурсных блоков в централизованной с ети со случайным трафиком.
Было проанализировано влияние интенсивности трафика на заполнение буферов при различном количестве абонентов (2, 4, 16 и 32):
Для 2 абонентов критическое значение λ составляет примерно 1.0, при превышении которого объем данных в буфере резко увеличивается, достигая ~8.7×10⁸ бит при λ=2.5.
Для 4 абонентов пороговое значение снижается до λ≈0.5, а максимальный объем буфера достигает ~1.5×10⁹ бит при λ=1.5.
При 16 абонентах критический порог падает до λ≈0.1, с максимальным размером буфера ~1.0×10⁹ бит при λ=0.3.
Для 32 абонентов пороговое значение составляет всего λ≈0.05, при этом размер буфера возрастает до ~3.0×10⁹ бит при λ=0.3.
Для всех конфигураций алгоритм Max Throughput демонстрирует наименьший объем данных в буфере, Equal Blind — наибольший, а Proportional Fair занимает промежуточное положение, особенно заметное при высокой нагрузке.
Приложение
Листинг 4 - Программная реализация моделирования
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import poisson, norm
import asyncio
import time
import nest_asyncio
# Параметры
R = 2500 # радиус зоны покрытия (м)
Ptx = 10 # мощность передатчика (Вт)
F0 = 900 # несущая частота (МГц)
KN = 2 # коэффициент потерь
N_RB = 25 # количество ресурсных блоков
packet_size = 8*1024 # размер пакета в битах
tau_RB = 0.5*10**(-3) # длительность слота (0.5 мс)
delta_f_RB = 180*10**3 # полоса частот
simulation_slots = 10**5 # число слотов
N2_lambda = [0, 1, 1.5, 2.0, 2.5]
N4_lambda = [0, 0.5, 0.75, 1.0, 1.5]
N16_lambda = [0, 0.1, 0.15, 0.2, 0.3]
N32_lambda = [0, 0.05, 0.1, 0.2, 0.3]
N_users_values = [2, 4, 16, 32] # число пользователей, shape: (4,)
# Параметры для модели Окамура-Хата
Hbs = 40 # Высота БС (м)
Hrx = 1.7 # Высота абонента (м)
S = 0 # Параметр затенения
K = 1.38 * 10**(-23) # Постоянная Больцмана
T = 300 # Абсолютная температура (К)
Pn = K * T * delta_f_RB * KN # Мощность шума
# Векторизованная функция для расчета потерь по модели Окамура-Хата
def okumura_hata_vectorized(d, f0, hbs, hrx, s):
# d: distances - shape: (N_users,) - расстояния от БС до каждого пользователя
# Возвращает: L_db - shape: (N_users,) - потери сигнала в дБ для каждого пользователя
# Преобразуем расстояние в километры
d_km = d / 1000 # shape: (N_users,)
# Расчет коэффициента a(hrx)
a_hrx = (1.1 * np.log10(f0) - 0.7) * hrx - (1.56 * np.log10(f0) - 0.8)
# Расчет потерь по модели Окамура-Хата
L_db = 46.3 + 33.9 * np.log10(f0) - 13.82 * np.log10(hbs) - a_hrx + (44.9 - 6.55 * np.log10(hrx)) * np.log10(d_km) + s # shape: (N_users,)
return L_db
# Векторизованная функция для расчета мощности принятого сигнала
def calculate_received_power_vectorized(ptx, L_db):
# L_db: shape (N_users, N_RB) - затухание сигнала в дБ
# Возвращает: Prx - shape (N_users, N_RB) - мощность принятого сигнала в ваттах
# L_db должен быть уже вычислен и передан
Prx = ptx / (10 ** (L_db / 10)) # Принятая мощность в ваттах, shape: (N_users, N_RB)
return Prx
# Векторизованная функция для расчета SNR
def calculate_snr_vectorized(ptx, L_db):
# L_db: shape (N_users, N_RB) - затухание сигнала в дБ
# Возвращает: snr - shape (N_users, N_RB) - отношение сигнал/шум
Prx = calculate_received_power_vectorized(ptx, L_db) # shape: (N_users, N_RB)
snr = Prx / Pn # shape: (N_users, N_RB)
return snr
# Векторизованная функция для расчета пропускной способности (скорости)
def calculate_data_rate_vectorized(snr, DF):
# snr: shape (N_users, N_RB) - отношение сигнал/шум
# DF: полоса частот
# Возвращает: shape (N_users, N_RB) - пропускная способность в бит/с
return DF * np.log2(1 + snr) # shape: (N_users, N_RB)
# Функция для размещения абонентов внутри окружности
def place_users(N, R):
# N: число пользователей
# R: радиус зоны покрытия
# Возвращает: radii - shape: (N,) - расстояния от БС до каждого пользователя
radii = np.sqrt(np.random.uniform(0, R**2, N)) # shape: (N,)
return radii
def model(N_users, lambda_val):
# N_users: число пользователей
# lambda_val: интенсивность поступления пакетов
# Возвращает: кортеж из трех значений - средние размеры буферов для трех алгоритмов
distances = place_users(N_users, R) # shape: (N_users,) - расстояния от БС до каждого пользователя
# Буферы для каждого алгоритма - словарь с тремя ключами, каждый содержит numpy array
# shape каждого массива: (N_users,) - размер буфера для каждого пользователя в битах
buffers = {'Equal Blind': np.zeros(N_users),
'Maximum Throughput': np.zeros(N_users),
'Proportional Fair': np.zeros(N_users)}
# Начальные средние скорости - словарь с тремя ключами, каждый содержит numpy array
# shape каждого массива: (N_users,) - средняя скорость передачи в бит/с для каждого пользователя
R_avg = {'Equal Blind': np.ones(N_users)*1e-6,
'Maximum Throughput': np.ones(N_users)*1e-6,
'Proportional Fair': np.ones(N_users)*1e-6}
# История буферов - словарь с тремя ключами, каждый содержит пустой список
# Будет наполняться суммарным размером буфера на каждой итерации
hist = {'Equal Blind': [], 'Max Throughput': [], 'Proportional Fair': []}
# Предварительно вычисляем базовые потери для всех пользователей
# shape: (N_users, 1) - базовое затухание сигнала в дБ для каждого пользователя
Li_base = okumura_hata_vectorized(distances, F0, Hbs, Hrx, S).reshape(N_users, 1)
# Предварительно выделяем память для массивов, которые будут многократно использоваться
Li = np.zeros((N_users, N_RB)) # shape: (N_users, N_RB) - затухание сигнала для каждого пользователя и ресурсного блока
C = np.zeros((N_users, N_RB)) # shape: (N_users, N_RB) - пропускная способность для каждого пользователя и ресурсного блока
V = np.zeros((N_users, N_RB)) # shape: (N_users, N_RB) - максимальный объем данных в битах, который можно передать
p_ij_k = np.zeros((N_users, N_RB)) # shape: (N_users, N_RB) - приоритеты для алгоритмов планирования
for k in range(simulation_slots):
# Генерируем случайные затухания (векторизованно)
# x_ij_k: shape: (N_users, N_RB) - случайная компонента затухания
x_ij_k = norm.rvs(0, 1, size=(N_users, N_RB))
# Li: shape: (N_users, N_RB) - полное затухание сигнала для каждого пользователя и ресурсного блока
Li = np.tile(np.mean(Li_base), (N_users, N_RB)) + x_ij_k
# Генерируем пакеты для всех пользователей сразу
# packets: shape: (N_users,) - число пакетов для каждого пользователя
packets = poisson.rvs(lambda_val, size=N_users)
# Вычисляем SNR и пропускную способность (векторизованно)
# snr_values: shape: (N_users, N_RB) - SNR для каждого пользователя и ресурсного блока
snr_values = calculate_snr_vectorized(Ptx, Li)
# C: shape: (N_users, N_RB) - пропускная способность в бит/с для каждого пользователя и ресурсного блока
C = calculate_data_rate_vectorized(snr_values, delta_f_RB)
# V: shape: (N_users, N_RB) - максимальный объем данных в битах, который можно передать за один слот
V = C * tau_RB
# Распределение ресурсов для каждого алгоритма
for algorithm in buffers:
# Расчет приоритетов (векторизованно)
if algorithm == 'Equal Blind':
# p_ij_k: shape: (N_users, N_RB) - приоритеты для алгоритма Equal Blind
p_ij_k = np.ones((N_users, N_RB)) / R_avg[algorithm].reshape(N_users, 1)
elif algorithm == 'Max Throughput':
# p_ij_k: shape: (N_users, N_RB) - приоритеты для алгоритма Max Throughput (равны пропускной способности)
p_ij_k = C.copy()
elif algorithm == 'Proportional Fair':
# p_ij_k: shape: (N_users, N_RB) - приоритеты для алгоритма Proportional Fair
p_ij_k = C / R_avg[algorithm].reshape(N_users, 1)
# Инициализация счетчика для отслеживания объема данных
# transmit_volume: shape: (N_users,) - объем переданных данных для каждого пользователя
transmit_volume = np.zeros(N_users)
# Добавляем пакеты в буферы (размер каждого пакета в битах умножается на число пакетов)
buffers[algorithm] += packets * packet_size # shape: (N_users,)
# Определяем активных пользователей (тех, у кого буфер не пуст)
# active_users: shape: (M,) - индексы активных пользователей, где M <= N_users
active_users = np.where(buffers[algorithm] > 0)[0]
if len(active_users) > 0:
# Инициализация массива распределения ресурсных блоков
# rb_allocation: shape: (N_RB,) - массив, показывающий какому пользователю назначен каждый ресурсный блок
rb_allocation = np.full(N_RB, -1, dtype=int)
# Вычисляем приоритеты только для активных пользователей
# active_priorities: shape: (M, N_RB) - приоритеты только для активных пользователей
active_priorities = p_ij_k[active_users]
# Распределяем ресурсные блоки одним проходом
for rb_index in range(N_RB):
if len(active_users) == 0:
break
# Находим индекс максимального приоритета среди активных пользователей
# best_user_local_idx: индекс пользователя с максимальным приоритетом в массиве active_users
best_user_local_idx = np.argmax(active_priorities[:, rb_index])
# selected_user_id: фактический индекс выбранного пользователя
selected_user_id = active_users[best_user_local_idx]
# Назначаем ресурсный блок выбранному пользователю
rb_allocation[rb_index] = selected_user_id
# Вычисляем объем данных для передачи (минимум из пропускной способности и размера буфера)
# data_to_transmit: объем данных для передачи в битах
data_to_transmit = min(V[selected_user_id, rb_index], buffers[algorithm][selected_user_id])
# Обновляем буфер и счетчик переданных данных
buffers[algorithm][selected_user_id] -= data_to_transmit
transmit_volume[selected_user_id] += data_to_transmit
# Если буфер пуст, удаляем пользователя из активных
if buffers[algorithm][selected_user_id] <= 0:
# Находим индекс в массиве active_users
# remove_idx: индекс пользователя в массиве active_users, которого нужно удалить
remove_idx = np.where(active_users == selected_user_id)[0][0]
# Удаляем пользователя и его приоритеты из соответствующих массивов
active_users = np.delete(active_users, remove_idx) # shape: (M-1,)
active_priorities = np.delete(active_priorities, remove_idx, axis=0) # shape: (M-1, N_RB)
# Сглаживающий фильтр для обновления средних скоростей
y = 1.
beta = tau_RB / y # коэффициент сглаживания
# Обновляем средние скорости для алгоритма, shape: (N_users,)
R_avg[algorithm] = (1-beta)*R_avg[algorithm] + beta*(transmit_volume/tau_RB)
# Добавляем текущий суммарный размер буфера в историю
hist[algorithm].append(np.sum(buffers[algorithm])) # сумма всех буферов
# Возвращаем средние значения истории буферов для трех алгоритмов
# Каждое значение - среднее по всем итерациям для данного алгоритма
return (np.mean(hist['Equal Blind']),
np.mean(hist['Maximum Throughput']),
np.mean(hist['Proportional Fair']))
async def model_async(N, lam):
"""Асинхронная обертка для функции model"""
try:
if lam == 0:
print(f"Пропуск моделирования для N={N}, λ={lam} (всегда нули)")
return (0, 0, 0), N, lam
# Передаем выполнение основной функции, которая может занять много времени
result = await asyncio.to_thread(model, N, lam)
print(f"Выполнено моделирование для N={N}, λ={lam}")
return result, N, lam
except Exception as e:
print(f"Ошибка при N={N}, λ={lam}: {str(e)}")
return (0, 0, 0), N, lam
async def main():
start_time = time.time()
results = {N: {'Equal Blind': [], 'Maximum Throughput': [], 'Proportional Fair': []} for N in N_users_values}
# Используем фиксированное начальное значение для воспроизводимости результатов
np.random.seed(42)
# Создаем список задач для асинхронного выполнения
tasks = []
for N in N_users_values:
if N == 2:
lambda_values = N2_lambda
elif N == 4:
lambda_values = N4_lambda
elif N == 16:
lambda_values = N16_lambda
elif N == 32:
lambda_values = N32_lambda
for lam in lambda_values:
print(f"Запуск моделирования: N={N}, λ={lam}")
tasks.append(model_async(N, lam))
# Запускаем все задачи параллельно и ждем результаты
completed_tasks = await asyncio.gather(*tasks)
# Обрабатываем результаты
for result, N, lam in completed_tasks:
eb, mt, pf = result
if N == 2:
lambda_values = N2_lambda
elif N == 4:
lambda_values = N4_lambda
elif N == 16:
lambda_values = N16_lambda
elif N == 32:
lambda_values = N32_lambda
lambda_index = list(lambda_values).index(lam)
results[N]['Equal Blind'].insert(lambda_index, eb)
results[N]['Maximum Throughput'].insert(lambda_index, mt)
results[N]['Proportional Fair'].insert(lambda_index, pf)
end_time = time.time()
print(f"Общее время выполнения: {end_time - start_time} секунд")
print(f"Моделирование успешно завершено для всех конфигураций!")
return results
nest_asyncio.apply()
results_optimized = asyncio.run(main())
results_plot = results_optimized
# Визуализация результатов
plt.figure(figsize=(16, 10))
for i, N in enumerate(N_users_values, 1):
plt.subplot(2, 2, i)
if N == 2:
lambda_values = N2_lambda
elif N == 4:
lambda_values = N4_lambda
elif N == 16:
lambda_values = N16_lambda
elif N == 32:
lambda_values = N32_lambda
plt.plot(lambda_values, results_plot[N]['Equal Blind'], linewidth=1, marker='o', linestyle='-', markersize=3, alpha=0.7, label='Equal Blind')
plt.plot(lambda_values, results_plot[N]['Maximum Throughput'], linewidth=1, marker='s', linestyle='--', markersize=3, alpha=0.7, label='Maximum Throughput')
plt.plot(lambda_values, results_plot[N]['Proportional Fair'], linewidth=1, marker='d', linestyle=':', markersize=3, alpha=0.7, label='Proportional Fair')
plt.title(f'N={N} абонентов', fontsize=14)
plt.xlabel('λ', fontsize=12)
plt.ylabel('Средний суммарный объем данных в буфере (бит)', fontsize=12)
plt.legend(fontsize=10)
plt.grid(True, linestyle='--', alpha=0.7)
plt.tick_params(axis='both', which='major', labelsize=10)
plt.tight_layout(pad=3.0)
plt.suptitle('Зависимость среднего суммарного объема данных в буфере от λ', fontsize=16, y=0.98)
plt.savefig('buffer_results.png', dpi=300, bbox_inches='tight')
plt.show()