# Модуль, позволяющий вычислить вероятность ошибки (символьной и битовой) 
# по заданному виду модуляции и отношению сигнал-шум Es/N0 на один символ
# модуляции. Es - средняя энергия символа модуляции, N0 - односторонняя
# спектральная плотность мощности белого шума.

import numpy as np
import scipy.special as sp
import scipy.stats as st
import scipy.optimize as opt

# Возвращает отношение мощностей v в децибелах
def pow2dB(v):
    return 10. * np.log10(v)

# Возвращает отношение мощностей v в разах
def dB2pow(v):
    return np.power(10., 0.1 * v)

# Возвращает отношение сигнал-шум на один символ модуляции
# EbN0 - отношение сигнал-шум на один бит
# R - скорость кодирования битов
# M - число бит на один символ модуляции
def EsN0(EbN0, R, M):
    return 1. * EbN0 * R * M

# Возвращает отношение сигнал-шум на один бит
# EsN0 - отношение сигнал-шум на один символ модуляции
# R - скорость кодирования битов
# M - число бит на один символ модуляции
def EbN0(EsN0, R, M):
    return 1. * EsN0 / (R * M)

# Возвращает вероятность символьной и битовой ошибки для модуляции ФМн (BPSK)
# Параметр когерентности coh: 0 - когерентная, 1 - частично-когерентная
def err_bpsk(EsN0, coh):
    q = np.sqrt(8. * EsN0)
    p = 0.5 * sp.erfc(q * 0.5 / np.sqrt(2.))
    if coh == 0:
        return p, p
    elif coh == 1:
        return 2. * p * (1. - p), 2. * p * (1. - p)
    else:
        return 0.5, 0.5 # TODO: добавить некогерентный вариант

# Возвращает вероятность символьной и битовой ошибки для модуляции ФМн-4/КАМ-4
# (QPSK) Параметр когерентности coh: 0 - когерентная, 1 - частично-когерентная
def err_qpsk(EsN0, coh):
    q = np.sqrt(8. * EsN0)
    p = 0.5 * sp.erfc(q * 0.5 / np.sqrt(2.))
    pc = p * (2. - p)
    if coh == 0:
        return pc, p
    elif coh == 1:
        return 2. * pc * (1. - pc), 2 * p * (1. - p)
    else:
        return 0.5, 0.5 # TODO: добавить некогерентный вариант

# Возвращает вероятность символьной и битовой ошибки для модуляции ЧМн (FSK)
# Параметр когерентности coh: 0 - когерентная, 1 - некогерентная
def err_fsk(EsN0, coh):
    q = np.sqrt(4. * EsN0)
    p = 0.5 * sp.erfc(q * 0.5 / np.sqrt(2.))
    if coh == 0:
        return p, p
    elif coh == 1:
        return 0.5 * np.exp(- 0.5 * EsN0), 0.5 * np.exp(- 0.5 * EsN0)
    else:
        return 0.5, 0.5 # TODO: выяснить что с частично-когерентным вариантом

# Возвращает оптимальный порог при некогерентной демодуляции АМн-сигнала
def find_threshold(q):
    f = lambda x: np.fabs( np.log(sp.ive(0., q * x)) - q * (q / 2. - x) )
    v0 = q / 2.
    return opt.minimize(f, v0).x[0]

# Возвращает вероятность символьной и битовой ошибки для модуляции АМн (ASK)
# Параметр когерентности coh: 0 - когерентная, 1 - некогерентная
def err_ask(EsN0, coh):
    q = np.sqrt(4. * EsN0)
    p = 0.5 * sp.erfc(q * 0.5 / np.sqrt(2.))
    if coh == 0:
        return p, p
    elif coh == 1:
        v = find_threshold(q)
        a, b = q, v
        if a == 0.:
            p21 = 0.5
            p22 = 0.
        else:
            p21 = 0.5 * np.exp(- v ** 2. / 2.)
            p22 = 0.5 * st.ncx2.cdf(b ** 2, 2., a ** 2)
        return p21 + p22, p21 + p22
    else:
        return 0.5, 0.5

# Возвращает (приближенно) вероятность символьной и битовой ошибки для модуляции
# ФМн-8 (8-PSK), когерентный прием. При q > 2 ошибка ps не превышает 1%.
# При q = 1 ошибка 2%, при q = 0.5 ошибка 4%, при q = 0 ошибка 14%
def err_psk_8(EsN0, coh):
    q = np.sqrt(2. * EsN0)
    ps = sp.erfc(q / np.sqrt(2.) * np.sin(np.pi / 8.))
    pb = 1. / 3. * ps # Код Грея
    return ps, pb
