ModulationPy: цифровые схемы модуляции на языке Python

от автора

Привет, Хабр!

Сегодня хочу поделиться своим небольшим домашним проектом:

ModulationPy (GitHub)

— модуль для моделирования цифровых схем модуляции (это которые PSK, QAM и т.п.). Проект был вдохновлен другой питоновской библиотекой: CommPy; однако, в рассмотренном классе задач с ней удалось даже немного посоревноваться!

Сигнальное созвездие 16-QAM сгенерированное и отрисованное с помощью ModulationPy
Сигнальное созвездие 16-QAM сгенерированное и отрисованное с помощью ModulationPy

На данный момент доступны два класса схем модуляции:

  • M-PSK: Phase Shift Keying (фазовая цифровая модуляция)

  • M-QAM: Quadratured Amplitude Modulation (квадратурная амплитудная модуляция)

    где M — это порядок модуляции.

Интересен модуль может быть, скорее всего, в разрезе образовательных целей в сфере беспроводной связи (подбор модуляций исходил именно из нее), однако, вдруг кому-то пригодится и для научных изысканий. Не MatLab’ом насущным едины!

Примеры использования

Итак, для начала скачиваем библиотеку с PyPI:

$ pip install ModulationPy

Либо устанавливаем из исходников, но на PyPI на момент написания статьи все-таки актуальная версия.

Зависимости две:

  • numpy>=1.7.1

  • matplotlib>=2.2.2 (для построения сигнальных созвездий)

Продемонстрируем на деле.

Итак, например, мы хотим использовать QPSK с поворотом фазы pi/4, двоичным входом и наложением по Грею (Gray mapping). Импортируем и инициализируем:

import numpy as np from ModulationPy import PSKModem  modem = PSKModem(4, np.pi/4,                  gray_map=True,                  bin_input=True)

Проверяем то ли мы инициализировали, нарисовав сигнальное созвездие методом plot_const():

modem.plot_const()
Сигнальное созвездие QPSK с поворотом фазы pi/4, двоичным входом и наложением по Грею (Gray mapping)
Сигнальное созвездие QPSK с поворотом фазы pi/4, двоичным входом и наложением по Грею (Gray mapping)

То же самое сделаем для 16-QAM (но для десятичных чисел на входе; указывать фазовый сдвиг не нужно — подразумевается наиболее распространенная прямоугольная QAM):

from ModulationPy import QAMModem  modem = QAMModem(16,                  gray_map=True,                   bin_input=False)  modem.plot_const()
Сигнальное созвездие 16-QAM с десятичным входом и наложением по Грею (Gray mapping)
Сигнальное созвездие 16-QAM с десятичным входом и наложением по Грею (Gray mapping)

На данный момент модуляция QAM реализована по примеру функции qammod в Octave [4]. И, да, реализованы только «четные» (в том смысле, что результат log2(M) — четное число) схемы модуляции (4-QAM, 16-QAM, 64-QAM). Пусть и не совсем полный набор, но как бы то ни было, в популярных стандартах беспроводной связи все равно нет «нечетных» схем модуляции (насколько я знаю).

Далее предлагаю перейти, собственно, к главному в модемах: к модуляции и демодуляции. Для этого нам понадобятся два метода:modulate() и demodulate() , доступные в обоих классах.

Метод modulate() принимает на вход всего один аргумент:

  • вектор входных значений (1-D ndarray of ints) — либо единиц и нулей, если выбрана опция bin_input=True , либо целых десятичных чисел от 0 до M-1, если bin_input=False ;

Методdemodulate() ожидает максимум два аргумента:

  • вектор, который должен быть демодулирован (1-D ndarray of complex symbols) ;

  • значение дисперсии аддитивного шума (float, по умолчанию 1.0).

Например, вот как это будет выглядеть для QPSK (двоичный вход/выход):

import numpy as np from ModulationPy import PSKModem  modem = PSKModem(4, np.pi/4,                   bin_input=True,                  soft_decision=False,                  bin_output=True)  msg = np.array([0, 0, 0, 1, 1, 0, 1, 1]) # input message  modulated = modem.modulate(msg) # modulation demodulated = modem.demodulate(modulated) # demodulation  print("Modulated message:\n"+str(modulated)) print("Demodulated message:\n"+str(demodulated))   >>>  Modulated message:    [0.70710678+0.70710678j  0.70710678-0.70710678j     -0.70710678+0.70710678j  -0.70710678-0.70710678j] >>> Demodulated message:    [0. 0. 0. 1. 1. 0. 1. 1.]

Или тоже QPSK, но уже с недвоичным входом / выходом:

import numpy as np from ModulationPy import PSKModem  modem = PSKModem(4, np.pi/4,                   bin_input=False,                  soft_decision=False,                  bin_output=False)  msg = np.array([0, 1, 2, 3]) # input message  modulated = modem.modulate(msg) # modulation demodulated = modem.demodulate(modulated) # demodulation  print("Modulated message:\n"+str(modulated)) print("Demodulated message:\n"+str(demodulated))  >>> Modulated message: [ 0.70710678+0.70710678j -0.70710678+0.70710678j  0.70710678-0.70710678j  -0.70710678-0.70710678j]   >>> Demodulated message: [0, 1, 2, 3]

Пример для 16-QAM (десятичный вход / выход):

import numpy as np from ModulationPy import QAMModem  modem = PSKModem(16,                   bin_input=False,                  soft_decision=False,                  bin_output=False)  msg = np.array([i for i in range(16)]) # input message  modulated = modem.modulate(msg) # modulation demodulated = modem.demodulate(modulated) # demodulation  print("Demodulated message:\n"+str(demodulated))  >>> Demodulated message: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

В общем и целом, я думаю, понятно. Доступные опции старался делать по примеру доступных в рамках матлабовского Communication Toolbox. Подробное описание приведено в README.md проекта.

BER performance

Продемонстрируем адекватно ли модемы работают в случае присутствия шума (возьмем классический АБГШ, он же AWGN), используя простейшую модель приема-передачи:

Структурная схема системы, состоящий из передатчика (источник сообщения, модулятор), канала (AWGN) и приемника (демодулятор и модуль подсчитывающий ошибки демодуляции).
Структурная схема системы, состоящий из передатчика (источник сообщения, модулятор), канала (AWGN) и приемника (демодулятор и модуль подсчитывающий ошибки демодуляции).

Сверяться будем с теоретическими кривыми [5] (если кому интересно, все формулы описаны тоже в README.md).

Исходные коды для моделирования представлены по ссылкам: M-PSK, M-QAM.

Результаты:

Кривые битовых ошибок для AWGN (M-PSK).
Кривые битовых ошибок для AWGN (M-PSK).
Кривые битовых ошибок для AWGN (M-QAM).
Кривые битовых ошибок для AWGN (M-QAM).

Да, с огрехами на малых значениях битовых ошибок (из-за относительно небольшого количества усреднений, я полагаю), однако…. it works!

Производительность

А теперь я хотел бы вернуться к вопросу «соревнования» с упомянутой выше CommPy.

Что нас отличает:

  • организация кода, стилистические различия (побочное, но не пренебрежимое);

  • я использовал более быстрый алгоритм демодуляции [6] (подробно описан в матлабовской документации [7], ну, и я добавил все в тот же README.md).

И вот, что получилось «забенчмаркать»:

Результаты:

Метод (библиотека)

Среднее время исполнения (мс)

modulation (ModulationPy): QPSK

10.3

modulation (CommPy): QPSK

15.7

demodulation (ModulationPy): QPSK

0.4

demodulation (CommPy): QPSK

319

modulation (ModulationPy): 256-QAM

8.9

modulation (CommPy): 256-QAM

11.3

demodulation (ModulationPy): 256-QAM

42.6

demodulation (CommPy): 256-QAM

22 000

Разработчикам CommPy результаты, вроде, понравились (см. данный issue) — поэтому возможно в обозримом будущем что-то из моего ModulationPy будет перекочевывать в CommPy (я не против, главное, чтобы пользу приносило). Но это, как говорится, поживем — увидим.

И, да, пусть результаты производительности и не дотянули до MatLab (по крайней мере исходя из данного примера: см. вкладку «Examples»), я все равно считаю достигнутое неплохим стартом!

Послесловие

Наверное, проекту не хватает еще некоторых видов модуляции (тех же 32-QAM и 128-QAM или же используемой в DVB-S2/S2X APSK), однако, честно скажу, что не могу обещать их скорого добавления.

Проект всегда был для меня в большей мере площадкой для изучения языка Python и библиотеки NumPy на практике (и сопутствующих инструментов: юнит-тесты (не успел правда в данном случае перейти на pytest — каюсь), CI (использую Travis), подготовка модуля для PyPi и т.д.), однако, теперь, слава богу, всему этому есть приложение и в рамках рабочих задач!

Однако, все же буду рад вашим issue и pull request’ам! И если возьметесь интегрировать наработки в CommPy, тоже будет очень круто!

В общем, не серчайте, если вдруг не отвечу достаточно быстро, и да пребудет с вами сила науки!

Литература и ссылки

  1. Haykin S. Communication systems. – John Wiley & Sons, 2008. — p. 93

  2. Goldsmith A. Wireless communications. – Cambridge university press, 2005. – p. 88-92

  3. MathWorks: comm.PSKModulator (https://www.mathworks.com/help/comm/ref/comm.pskmodulator-system-object.html?s_tid=doc_ta)

  4. Octave: qammod (https://octave.sourceforge.io/communications/function/qammod.html)

  5. Link Budget Analysis: Digital Modulation, Part 3 (www.AtlantaRF.com)

  6. Viterbi, A. J. (1998). An intuitive justification and a simplified implementation of the MAP decoder for convolutional codes. IEEE Journal on Selected Areas in Communications, 16(2), 260-264.

  7. MathWorks: Approximate LLR Algorithm (https://www.mathworks.com/help/comm/ug/digital-modulation.html#brc6ymu)

ссылка на оригинал статьи https://habr.com/ru/post/552242/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *