diff --git a/optic/comm/metrics.py b/optic/comm/metrics.py index 17634c79..a858b78b 100644 --- a/optic/comm/metrics.py +++ b/optic/comm/metrics.py @@ -381,7 +381,7 @@ def monteCarloMI(rx, tx, M, constType, px=None): for k in range(nModes): σ2 = noiseVar[k] - MI[k] = calcMI(rx[:, k], tx[:, k], σ2, constSymb, px) + MI[k] = calcMI(rx[:, k], tx[:, k], σ2, constSymb, px)[0] return MI diff --git a/tests/test_dsp.py b/tests/test_dsp.py new file mode 100644 index 00000000..a1eed577 --- /dev/null +++ b/tests/test_dsp.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +""" +Test functions in dsp.py + +""" + +from optic.dsp.core import finddelay +import numpy as np + + +def test_finddelay_for_arrays_of_real_values(): + delay = 35 + + a = np.arange(0, 100) + b = np.roll(a, -delay) + + assert delay == finddelay(a, b) + assert delay == -finddelay(b, a) + + +def test_finddelay_for_arrays_of_complex_values(): + delay = 57 + + a = np.arange(0, 100) + 1j * np.arange(0, 100) + b = np.roll(a, -delay) + + assert delay == finddelay(a, b) + assert delay == -finddelay(b, a) diff --git a/tests/test_metrics.py b/tests/test_metrics.py new file mode 100644 index 00000000..a6f17c1e --- /dev/null +++ b/tests/test_metrics.py @@ -0,0 +1,97 @@ +import unittest +import numpy as np +from optic.dsp.core import pnorm +from optic.models.channels import awgn +from optic.comm.modulation import modulateGray +from optic.comm.metrics import ( + fastBERcalc, + calcLLR, + monteCarloGMI, + monteCarloMI, + Qfunc, + calcEVM, + theoryBER, + calcLinOSNR, + GN_Model_NyquistWDM, + ASE_NyquistWDM, + GNmodel_OSNR, +) + +class TestCommunicationMetrics(unittest.TestCase): + + def setUp(self): + # Set up common parameters or fixtures if needed + pass + + def test_fastBERcalc(self): + # Add test cases for fastBERcalc + + # Run BER vs Ebn0 Monte Carlo simulation in the AWGN channel + + qamOrder = [4, 16, 64, 256] # Modulation order + + EbN0dB_ = np.arange(10, 25.5, 0.5) + BER = np.zeros((len(EbN0dB_),len(qamOrder))) + BER_theory = np.zeros((len(EbN0dB_),len(qamOrder))) + + for ii, M in enumerate(qamOrder): + + for indSNR in range(EbN0dB_.size): + + EbN0dB = EbN0dB_[indSNR] + + # generate random bits + bitsTx = np.random.randint(2, size=int(np.log2(M)*1e5)) + + # Map bits to constellation symbols + symbTx = modulateGray(bitsTx, M, 'qam') + + # Normalize symbols energy to 1 + symbTx = pnorm(symbTx) + + # AWGN channel + snrdB = EbN0dB + 10*np.log10(np.log2(M)) + symbRx = awgn(symbTx, snrdB) + + # BER calculation + BER[indSNR, ii] = fastBERcalc(symbRx, symbTx, M, 'qam')[0][0] + BER_theory[indSNR, ii] = theoryBER(M, EbN0dB, 'qam') + + if BER[indSNR, ii] == 0: + break + + np.testing.assert_array_almost_equal(BER, BER_theory, decimal=3) + + def test_monteCarlo_MI_and_GMI(self): + # Run GMI vs SNR Monte Carlo simulation + qamOrder = [4, 16, 64] # Modulation order + + SNR = np.arange(15, 26, 1) + MI = np.zeros((len(SNR),len(qamOrder))) + GMI = np.zeros((len(SNR),len(qamOrder))) + + for ii, M in enumerate(qamOrder): + for indSNR in range(SNR.size): + + snrdB = SNR[indSNR] + + # generate random bits + bitsTx = np.random.randint(2, size=int(np.log2(M)*1e5)) + + # Map bits to constellation symbols + symbTx = modulateGray(bitsTx, M, 'qam') + + # Normalize symbols energy to 1 + symbTx = pnorm(symbTx) + + # AWGN channel + symbRx = awgn(symbTx, snrdB) + + # GMI estimation + MI[indSNR, ii] = monteCarloMI(symbRx, symbTx, M, 'qam')[0] + GMI[indSNR, ii] = monteCarloGMI(symbRx, symbTx, M, 'qam')[0][0] + + np.testing.assert_array_almost_equal(MI, GMI, decimal=1) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_modulation.py b/tests/test_modulation.py new file mode 100644 index 00000000..8456ffe4 --- /dev/null +++ b/tests/test_modulation.py @@ -0,0 +1,56 @@ +import unittest +import numpy as np +from optic.utils import bitarray2dec, dec2bitarray +from optic.comm.modulation import grayCode, grayMapping, minEuclid, demap, modulateGray, demodulateGray + +class TestModulationFunctions(unittest.TestCase): + def test_GrayCode(self): + # Test GrayCode function for different values of n + for n in range(1, 6): + with self.subTest(n=n): + result = grayCode(n) + self.assertEqual(len(result), 2**n) + self.assertEqual(len(result[0]), n) + + def test_GrayMapping(self): + # Test GrayMapping function for different values of M and constType + M_values = [4, 16, 64] + constTypes = ['qam', 'psk', 'pam'] + for M in M_values: + for constType in constTypes: + with self.subTest(M=M, constType=constType): + result = grayMapping(M, constType) + self.assertEqual(len(result), M) + + def test_minEuclid(self): + # Test minEuclid function with some example inputs + symb = np.array([1+1j, 2+2j, 3+3j]) + const = np.array([1+1j, 3+3j, 2+2j]) + result = minEuclid(symb, const) + np.testing.assert_array_equal(result, np.array([0, 2, 1])) + + def test_demap(self): + # Test demap function with some example inputs + indSymb = np.array([0, 2, 1]) + bitMap = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]) + result = demap(indSymb, bitMap) + np.testing.assert_array_equal(result, np.array([0, 0, 1, 0, 0, 1])) + + def test_modulateGray(self): + # Test modulateGray function with some example inputs + bits = np.array([0, 1, 0, 0, 1, 0]) + M = 4 + constType = 'qam' + result = modulateGray(bits, M, constType) + self.assertEqual(len(result), len(bits) // int(np.log2(M))) + + def test_demodulateGray(self): + # Test demodulateGray function with some example inputs + symb = np.array([1+1j, 3+3j, 2+2j]) + M = 4 + constType = 'qam' + result = demodulateGray(symb, M, constType) + self.assertEqual(len(result), len(symb) * int(np.log2(M))) + +if __name__ == '__main__': + unittest.main()