From b5a6022cfa18979ea7edde148f58e335abbd3357 Mon Sep 17 00:00:00 2001 From: lboegner Date: Mon, 8 May 2023 11:32:34 -0400 Subject: [PATCH 01/11] Change README logo if in dark mode (#95) --- README.md | 5 ++++- docs/torchsig_logo_white_dodgerblue.png | Bin 0 -> 48761 bytes 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 docs/torchsig_logo_white_dodgerblue.png diff --git a/README.md b/README.md index f85ddd8..d1bd9ec 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@

- drawing + + + +

----- diff --git a/docs/torchsig_logo_white_dodgerblue.png b/docs/torchsig_logo_white_dodgerblue.png new file mode 100644 index 0000000000000000000000000000000000000000..d11e6bee6d38a8a96fdc2621085340013f07a930 GIT binary patch literal 48761 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 literal 0 HcmV?d00001 From c5b94672eb2e355e787ad2a4a098dbfd7a6b5601 Mon Sep 17 00:00:00 2001 From: lboegner Date: Mon, 8 May 2023 11:38:58 -0400 Subject: [PATCH 02/11] Start transform tests & minor `RandomTimeShift` optimization (#94) * Start transform tests & slight optimization in RandomTimeShift * Add new length check for TimeCrop tests * Code style --- tests/test_transforms.py | 121 ++++++++++++++++++++ torchsig/transforms/system_impairment/si.py | 27 +++-- 2 files changed, 136 insertions(+), 12 deletions(-) create mode 100644 tests/test_transforms.py diff --git a/tests/test_transforms.py b/tests/test_transforms.py new file mode 100644 index 0000000..3bde6f5 --- /dev/null +++ b/tests/test_transforms.py @@ -0,0 +1,121 @@ +from unittest import TestCase +from torchsig.transforms.system_impairment.si import RandomTimeShift, TimeCrop +import numpy as np + + +class RandomTimeShiftTests(TestCase): + def test_random_time_shift_right(self): + rng = np.random.RandomState(0) + data = ( + rng.rand( + 16, + ) + - 0.5 + ) + 1j * ( + rng.rand( + 16, + ) + - 0.5 + ) + shift = 5 + t = RandomTimeShift( + shift=shift, + ) + new_data = t(data) + self.assertTrue(np.allclose(data[:-shift], new_data[shift:])) + self.assertTrue(np.allclose(new_data[:shift], np.zeros(shift))) + + def test_random_time_shift_left(self): + rng = np.random.RandomState(0) + data = ( + rng.rand( + 16, + ) + - 0.5 + ) + 1j * ( + rng.rand( + 16, + ) + - 0.5 + ) + shift = -5 + t = RandomTimeShift( + shift=shift, + ) + new_data = t(data) + self.assertTrue(np.allclose(data[-shift:], new_data[:shift])) + self.assertTrue(np.allclose(new_data[shift:], np.zeros(np.abs(shift)))) + + +class TimeCrop(TestCase): + def test_time_crop_start(self): + rng = np.random.RandomState(0) + num_iq_samples = 16 + data = ( + rng.rand( + num_iq_samples, + ) + - 0.5 + ) + 1j * ( + rng.rand( + num_iq_samples, + ) + - 0.5 + ) + length = 4 + t = TimeCrop( + crop_type="start", + length=length, + ) + new_data: np.ndarray = t(data) + self.assertTrue(np.allclose(data[:length], new_data)) + self.assertTrue(new_data.shape[0] == length) + + def test_time_crop_center(self): + rng = np.random.RandomState(0) + num_iq_samples = 16 + data = ( + rng.rand( + num_iq_samples, + ) + - 0.5 + ) + 1j * ( + rng.rand( + num_iq_samples, + ) + - 0.5 + ) + length = 4 + t = TimeCrop( + crop_type="center", + length=length, + ) + new_data: np.ndarray = t(data) + extra_samples = num_iq_samples - length + self.assertTrue( + np.allclose(data[extra_samples // 2 : -extra_samples // 2], new_data) + ) + self.assertTrue(new_data.shape[0] == length) + + def test_time_crop_end(self): + rng = np.random.RandomState(0) + num_iq_samples = 16 + data = ( + rng.rand( + num_iq_samples, + ) + - 0.5 + ) + 1j * ( + rng.rand( + num_iq_samples, + ) + - 0.5 + ) + length = 4 + t = TimeCrop( + crop_type="end", + length=length, + ) + new_data: np.ndarray = t(data) + self.assertTrue(np.allclose(data[-length:], new_data)) + self.assertTrue(new_data.shape[0] == length) diff --git a/torchsig/transforms/system_impairment/si.py b/torchsig/transforms/system_impairment/si.py index 97e33c6..a6fa370 100644 --- a/torchsig/transforms/system_impairment/si.py +++ b/torchsig/transforms/system_impairment/si.py @@ -67,12 +67,13 @@ def __call__(self, data: Any) -> Any: ) # Apply data transformation - new_data.iq_data = functional.fractional_shift( - data.iq_data, - self.taps, - self.interp_rate, - -decimal_part # this needed to be negated to be consistent with the previous implementation - ) + if decimal_part != 0: + new_data.iq_data = functional.fractional_shift( + data.iq_data, + self.taps, + self.interp_rate, + -decimal_part # this needed to be negated to be consistent with the previous implementation + ) new_data.iq_data = functional.time_shift(new_data.iq_data, int(integer_part)) # Update SignalDescription @@ -91,12 +92,14 @@ def __call__(self, data: Any) -> Any: new_data.signal_description = new_signal_description else: - new_data = functional.fractional_shift( - data, - self.taps, - self.interp_rate, - -decimal_part # this needed to be negated to be consistent with the previous implementation - ) + new_data = data.copy() + if decimal_part != 0: + new_data = functional.fractional_shift( + new_data, + self.taps, + self.interp_rate, + -decimal_part # this needed to be negated to be consistent with the previous implementation + ) new_data = functional.time_shift(new_data, int(integer_part)) return new_data From caaeb936daa1270309841bf89e92ab82a0ef335f Mon Sep 17 00:00:00 2001 From: Garrett Vanhoy Date: Mon, 8 May 2023 11:42:10 -0400 Subject: [PATCH 03/11] Fixed name mangling. --- tests/test_transforms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 3bde6f5..2e9c821 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -3,7 +3,7 @@ import numpy as np -class RandomTimeShiftTests(TestCase): +class RandomTimeShiftTestCase(TestCase): def test_random_time_shift_right(self): rng = np.random.RandomState(0) data = ( @@ -47,7 +47,7 @@ def test_random_time_shift_left(self): self.assertTrue(np.allclose(new_data[shift:], np.zeros(np.abs(shift)))) -class TimeCrop(TestCase): +class TimeCropTestCase(TestCase): def test_time_crop_start(self): rng = np.random.RandomState(0) num_iq_samples = 16 From 8dd0d6aa7fb12dc5989d92588bed0316a372b658 Mon Sep 17 00:00:00 2001 From: MattCarrickPL <120057274+MattCarrickPL@users.noreply.github.com> Date: Mon, 8 May 2023 16:16:38 -0400 Subject: [PATCH 04/11] QAM/PSK Pulse shaping filter transition bandwidth corrected (#98) * excess bandwidth is defined in porportion to signal bandwidth, not sampling rate, thus needs to be scaled by the samples per symbol * filling in a comment to describe modification to code --- torchsig/datasets/synthetic.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/torchsig/datasets/synthetic.py b/torchsig/datasets/synthetic.py index 330c631..daff017 100644 --- a/torchsig/datasets/synthetic.py +++ b/torchsig/datasets/synthetic.py @@ -366,8 +366,10 @@ def _generate_samples(self, item: Tuple) -> np.ndarray: (self.iq_samples_per_symbol * len(symbols),), dtype=np.complex64 ) zero_padded[:: self.iq_samples_per_symbol] = symbols + # excess bandwidth is defined in porportion to signal bandwidth, not sampling rate, + # thus needs to be scaled by the samples per symbol pulse_shape_filter_length = estimate_filter_length( - signal_description.excess_bandwidth + signal_description.excess_bandwidth/self.iq_samples_per_symbol ) pulse_shape_filter_span = int( (pulse_shape_filter_length - 1) / 2 From cedb8c563b0dee1adbfdf7e064cfe7fb79fd1233 Mon Sep 17 00:00:00 2001 From: MattCarrickPL <120057274+MattCarrickPL@users.noreply.github.com> Date: Mon, 8 May 2023 16:20:42 -0400 Subject: [PATCH 05/11] QAM/PSK Pulse shaping filter transition bandwidth corrected (#98) * excess bandwidth is defined in porportion to signal bandwidth, not sampling rate, thus needs to be scaled by the samples per symbol * filling in a comment to describe modification to code From e92701bfc1a0162686b384e1180352ccd99be3a6 Mon Sep 17 00:00:00 2001 From: MattCarrickPL <120057274+MattCarrickPL@users.noreply.github.com> Date: Mon, 8 May 2023 16:21:43 -0400 Subject: [PATCH 06/11] OFDM Modulator filter lengths estimated and bandwidth randomized (#99) * * cutoff frequency for LPF now randomized when using 'rand_lpf' * derives a transition bandwidth from the cutoff frequency * uses filter length approximating function for the randomized LPF * using filter estimation function for pre-computed LPF taps --- torchsig/datasets/synthetic.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/torchsig/datasets/synthetic.py b/torchsig/datasets/synthetic.py index daff017..51b26f2 100644 --- a/torchsig/datasets/synthetic.py +++ b/torchsig/datasets/synthetic.py @@ -503,14 +503,16 @@ def __init__( self.index = [] if "lpf" in sidelobe_suppression_methods: # Precompute LPF - num_taps = 50 - cutoff = 0.6 + cutoff = 0.3 + transition_bandwidth = (0.5-cutoff)/4 + num_taps = estimate_filter_length(transition_bandwidth) self.taps = sp.firwin( num_taps, cutoff, - width=cutoff * 0.02, + width=transition_bandwidth, window=sp.get_window("blackman", num_taps), scale=True, + fs=1 ) # Precompute all possible random symbols for speed at sample generation @@ -740,14 +742,16 @@ def _generate_samples(self, item: Tuple) -> np.ndarray: elif sidelobe_suppression_method == "rand_lpf": flattened = cyclic_prefixed.T.flatten() # Generate randomized LPF - cutoff = np.random.uniform(0.95, 0.95) - num_taps = estimate_filter_length(cutoff) + cutoff = np.random.uniform(0.25, 0.475) + transition_bandwidth = (0.5-cutoff)/4 + num_taps = estimate_filter_length(transition_bandwidth) taps = sp.firwin( num_taps, cutoff, - width=cutoff * 0.02, + width=transition_bandwidth, window=sp.get_window("blackman", num_taps), scale=True, + fs=1 ) # Apply random LPF output = torchsig_convolve(flattened, taps, gpu=self.use_gpu)[:-num_taps] From 1e4ede4af0e273ef6ac6ea2947b285318c893b2c Mon Sep 17 00:00:00 2001 From: Garrett Vanhoy Date: Fri, 19 May 2023 13:10:16 -0400 Subject: [PATCH 07/11] Tests for visual inspection. (#103) --- .github/workflows/pytest.yml | 2 +- .gitignore | 1 + tests/figures/.gitkeep | 0 tests/test_modulation_figures.py | 119 +++++++++++++++++++++++++++++++ torchsig/datasets/synthetic.py | 3 +- 5 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 tests/figures/.gitkeep create mode 100644 tests/test_modulation_figures.py diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 1d1693a..041f75f 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -32,4 +32,4 @@ jobs: - name: Test with pytest run: | pip install pytest - pytest \ No newline at end of file + pytest --ignore-glob=*_figures.py \ No newline at end of file diff --git a/.gitignore b/.gitignore index e8d6acb..34ef6f6 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ build/ checkpoints/ lightning_logs/ *.pt +*.jpg diff --git a/tests/figures/.gitkeep b/tests/figures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_modulation_figures.py b/tests/test_modulation_figures.py new file mode 100644 index 0000000..3efcf07 --- /dev/null +++ b/tests/test_modulation_figures.py @@ -0,0 +1,119 @@ +from torchsig.datasets.synthetic import ( + ConstellationDataset, + FSKDataset, + OFDMDataset, + default_const_map, + freq_map, +) +from matplotlib import pyplot as plt +import numpy as np +import pytest + + +@pytest.mark.parametrize("modulation_name", default_const_map.keys()) +def test_can_generate_constellation_figures(modulation_name): + dataset = ConstellationDataset( + [modulation_name], + num_iq_samples=4096, + num_samples_per_class=1, + iq_samples_per_symbol=2, + pulse_shape_filter=None, + random_pulse_shaping=False, + random_data=False, + use_gpu=False, + ) + item = dataset[0] + iq_data: np.ndarray = item[0] + + # IQ Data + plt.figure(figsize=(9, 4)) + plt.subplot(2, 2, 1) + plt.plot(iq_data.real) + plt.plot(iq_data.imag) + plt.legend(["real", "imaginary"]) + plt.title("IQ Data") + + plt.subplot(2, 2, 2) + _ = plt.scatter(iq_data.real, iq_data.imag) + plt.title("Constellation") + + plt.subplot(2, 2, 3) + _ = plt.psd(iq_data) + plt.title("PSD") + + plt.subplot(2, 2, 4) + _ = plt.specgram(iq_data) + plt.title("Spectrogram") + plt.savefig("tests/figures/synthetic_{}.jpg".format(modulation_name)) + + +@pytest.mark.parametrize("modulation_name", freq_map.keys()) +def test_can_generate_fsk_figures(modulation_name): + dataset = FSKDataset( + [modulation_name], + num_iq_samples=4096, + num_samples_per_class=1, + iq_samples_per_symbol=2, + random_pulse_shaping=False, + random_data=False, + use_gpu=False, + ) + item = dataset[0] + iq_data: np.ndarray = item[0] + + # IQ Data + plt.figure(figsize=(9, 4)) + plt.subplot(2, 2, (1, 2)) + plt.plot(iq_data.real[:256]) + plt.plot(iq_data.imag[:256]) + plt.legend(["real", "imaginary"]) + plt.title("IQ Data") + + plt.subplot(2, 2, 3) + _ = plt.psd(iq_data) + plt.title("PSD") + + plt.subplot(2, 2, 4) + _ = plt.specgram(iq_data) + plt.title("Spectrogram") + plt.savefig("tests/figures/synthetic_{}.jpg".format(modulation_name)) + + +num_subcarriers = (64, 72, 128, 180, 256, 300, 512, 600, 900, 1024, 1200, 2048) + + +@pytest.mark.parametrize("num_subcarriers", num_subcarriers) +def test_can_generate_ofdm_figures(num_subcarriers): + constellations = ("bpsk", "qpsk", "16qam", "64qam", "256qam", "1024qam") + sidelobe_suppression_methods = ("lpf", "win_start") + dataset = OFDMDataset( + constellations, + num_subcarriers=(num_subcarriers,), + num_iq_samples=4096, + num_samples_per_class=1, + sidelobe_suppression_methods=sidelobe_suppression_methods, + use_gpu=False, + ) + item = dataset[0] + iq_data: np.ndarray = item[0] + + # IQ Data + plt.figure(figsize=(9, 4)) + plt.subplot(2, 2, (1, 2)) + plt.plot(iq_data.real[:256]) + plt.plot(iq_data.imag[:256]) + plt.legend(["real", "imaginary"]) + plt.title("IQ Data") + + plt.subplot(2, 2, 3) + _ = plt.psd(iq_data) + plt.title("PSD") + + plt.subplot(2, 2, 4) + _ = plt.specgram(iq_data) + plt.title("Spectrogram") + plt.savefig("tests/figures/synthetic_ofdm_{}.jpg".format(num_subcarriers)) + + +if __name__ == "__main__": + pytest.main() diff --git a/torchsig/datasets/synthetic.py b/torchsig/datasets/synthetic.py index 51b26f2..c2bf140 100644 --- a/torchsig/datasets/synthetic.py +++ b/torchsig/datasets/synthetic.py @@ -602,12 +602,11 @@ def _generate_samples(self, item: Tuple) -> np.ndarray: if sym_mult < 1.0 else int(np.ceil(sym_mult)) ) - + if self.num_iq_samples > 32768: # assume wideband task and reduce data for speed sym_mult = 0.3 - if mod_type == "random": # Randomized subcarrier modulations symbols = [] From 8b9847bd303960f4448eb4d69e534d28c780f418 Mon Sep 17 00:00:00 2001 From: Garrett Vanhoy Date: Fri, 19 May 2023 13:43:22 -0400 Subject: [PATCH 08/11] 91 create generation performance benchmarks for each modulation type (#104) * Initial benchmarking code. * Some benchmarks * Adding initial benchmarks. * Fix action. --------- --- .github/workflows/pytest.yml | 2 +- .gitignore | 1 + tests/test_modulation_benchmark.py | 69 ++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 tests/test_modulation_benchmark.py diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 041f75f..25e841e 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -32,4 +32,4 @@ jobs: - name: Test with pytest run: | pip install pytest - pytest --ignore-glob=*_figures.py \ No newline at end of file + pytest --ignore-glob=*_figures.py --ignore-glob=*_benchmark.py \ No newline at end of file diff --git a/.gitignore b/.gitignore index 34ef6f6..4e0f4eb 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ checkpoints/ lightning_logs/ *.pt *.jpg +*.benchmarks/ diff --git a/tests/test_modulation_benchmark.py b/tests/test_modulation_benchmark.py new file mode 100644 index 0000000..d928a3e --- /dev/null +++ b/tests/test_modulation_benchmark.py @@ -0,0 +1,69 @@ +from torchsig.datasets.synthetic import ( + ConstellationDataset, + FSKDataset, + OFDMDataset, + default_const_map, + freq_map, +) +from matplotlib import pyplot as plt +import numpy as np +import pytest + + +def iterate_one_epoch(dataset): + for _ in dataset: + pass + + +@pytest.mark.benchmark(group="constellation") +@pytest.mark.parametrize("modulation_name", default_const_map.keys()) +def test_generate_constellation_benchmark(benchmark, modulation_name): + dataset = ConstellationDataset( + [modulation_name], + num_iq_samples=4096, + num_samples_per_class=100, + iq_samples_per_symbol=2, + pulse_shape_filter=None, + random_pulse_shaping=False, + random_data=False, + use_gpu=False, + ) + benchmark(iterate_one_epoch, dataset) + + +@pytest.mark.benchmark(group="fsk") +@pytest.mark.parametrize("modulation_name", freq_map.keys()) +def test_generate_fsk_benchmark(benchmark, modulation_name): + dataset = FSKDataset( + [modulation_name], + num_iq_samples=4096, + num_samples_per_class=100, + iq_samples_per_symbol=2, + random_pulse_shaping=False, + random_data=False, + use_gpu=False, + ) + benchmark(iterate_one_epoch, dataset) + + +num_subcarriers = (64, 72, 128, 180, 256, 300, 512, 600, 900, 1024, 1200, 2048) + + +@pytest.mark.benchmark(group="ofdm") +@pytest.mark.parametrize("num_subcarriers", num_subcarriers) +def test_generate_ofdm_benchmark(benchmark, num_subcarriers): + constellations = ("bpsk", "qpsk", "16qam", "64qam", "256qam", "1024qam") + sidelobe_suppression_methods = ("lpf", "win_start") + dataset = OFDMDataset( + constellations, + num_subcarriers=(num_subcarriers,), + num_iq_samples=4096, + num_samples_per_class=100, + sidelobe_suppression_methods=sidelobe_suppression_methods, + use_gpu=False, + ) + benchmark(iterate_one_epoch, dataset) + + +if __name__ == "__main__": + pytest.main() From da8845879c5de3ad253b79e1f1a608c56008c758 Mon Sep 17 00:00:00 2001 From: Garrett Vanhoy Date: Mon, 22 May 2023 09:44:23 -0400 Subject: [PATCH 09/11] 75 examine ofdm generation for potential speedups for sig53 (#105) * Tests for visual inspection of modulation generation. (#102) * Optimizations show significant improvement in generation speed. * Nominal behavior after using scipy. --- torchsig/datasets/synthetic.py | 94 +++++++++++++--------------------- 1 file changed, 37 insertions(+), 57 deletions(-) diff --git a/torchsig/datasets/synthetic.py b/torchsig/datasets/synthetic.py index c2bf140..17560c8 100644 --- a/torchsig/datasets/synthetic.py +++ b/torchsig/datasets/synthetic.py @@ -16,21 +16,22 @@ def torchsig_convolve( signal: np.ndarray, taps: np.ndarray, gpu: bool = False ) -> np.ndarray: + return sp.convolve(signal, taps, "same") # This will run into issues is signal is smaller than taps - torch_signal = torch.from_numpy(signal.astype(np.complex128)).reshape(1, -1) - torch_taps = torch.flip( - torch.from_numpy(taps.astype(np.complex128)).reshape(1, 1, -1), dims=(2,) - ) - if gpu: - result = torch.nn.functional.conv1d( - torch_signal.cuda(), torch_taps.cuda(), padding=torch_signal.shape[0] - 1 - ) - return result.cpu().numpy()[0] - - result = torch.nn.functional.conv1d( - torch_signal, torch_taps, padding=torch_signal.shape[0] - 1 - ) - return result.numpy()[0] + # torch_signal = torch.from_numpy(signal.astype(np.complex128)).reshape(1, -1) + # torch_taps = torch.flip( + # torch.from_numpy(taps.astype(np.complex128)).reshape(1, 1, -1), dims=(2,) + # ) + # if gpu: + # result = torch.nn.functional.conv1d( + # torch_signal.cuda(), torch_taps.cuda(), padding=torch_signal.shape[0] - 1 + # ) + # return result.cpu().numpy()[0] + + # result = torch.nn.functional.conv1d( + # torch_signal, torch_taps, padding=torch_signal.shape[0] - 1 + # ) + # return result.numpy()[0] def remove_corners(const): @@ -359,7 +360,7 @@ def _generate_samples(self, item: Tuple) -> np.ndarray: const = self.const_map[class_name] / np.mean(np.abs(self.const_map[class_name])) symbol_nums = np.random.randint( - 0, len(const), 2 * int(self.num_iq_samples / self.iq_samples_per_symbol) + 0, len(const), int(self.num_iq_samples / self.iq_samples_per_symbol) ) symbols = const[symbol_nums] zero_padded = np.zeros( @@ -369,7 +370,7 @@ def _generate_samples(self, item: Tuple) -> np.ndarray: # excess bandwidth is defined in porportion to signal bandwidth, not sampling rate, # thus needs to be scaled by the samples per symbol pulse_shape_filter_length = estimate_filter_length( - signal_description.excess_bandwidth/self.iq_samples_per_symbol + signal_description.excess_bandwidth / self.iq_samples_per_symbol ) pulse_shape_filter_span = int( (pulse_shape_filter_length - 1) / 2 @@ -504,7 +505,7 @@ def __init__( if "lpf" in sidelobe_suppression_methods: # Precompute LPF cutoff = 0.3 - transition_bandwidth = (0.5-cutoff)/4 + transition_bandwidth = (0.5 - cutoff) / 4 num_taps = estimate_filter_length(transition_bandwidth) self.taps = sp.firwin( num_taps, @@ -512,7 +513,7 @@ def __init__( width=transition_bandwidth, window=sp.get_window("blackman", num_taps), scale=True, - fs=1 + fs=1, ) # Precompute all possible random symbols for speed at sample generation @@ -585,49 +586,28 @@ def _generate_samples(self, item: Tuple) -> np.ndarray: if not self.random_data: np.random.seed(index) - # Symbol multiplier: we want to be able to randomly index into - # generated IQ samples such that we can see symbol transitions. - # This multiplier ensures enough OFDM symbols are generated for - # this randomness. - # Check against max possible requirements - # 2x for symbol length - # 2x for number of symbols for at least 1 transition - # 4x for largest burst duration option - sym_mult = 1 - - if self.num_iq_samples <= 4 * 2 * 2 * num_subcarriers: - sym_mult = self.num_iq_samples / (2 * 2 * num_subcarriers) + 1e-6 - sym_mult = ( - int(np.ceil(sym_mult**-1)) - if sym_mult < 1.0 - else int(np.ceil(sym_mult)) - ) - - if self.num_iq_samples > 32768: - # assume wideband task and reduce data for speed - sym_mult = 0.3 - if mod_type == "random": - # Randomized subcarrier modulations - symbols = [] - for subcarrier_idx in range(num_subcarriers): - curr_const = np.random.randint(len(self.random_symbols)) - symbols.extend( - np.random.choice( - self.random_symbols[curr_const], - size=int(2 * sym_mult * self.num_iq_samples / num_subcarriers), + symbols_idxs = np.random.randint(0, 1024, size=self.num_iq_samples) + const_idxes = np.random.choice( + range(len(self.random_symbols)), size=num_subcarriers + ) + symbols = np.zeros(self.num_iq_samples, dtype=np.complex128) + for subcarrier_idx, const_idx in enumerate(const_idxes): + begin_idx = (self.num_iq_samples) * subcarrier_idx + end_idx = (self.num_iq_samples) * (subcarrier_idx + 1) + symbols[begin_idx:end_idx] = self.random_symbols[const_idx][ + np.mod( + symbols_idxs[begin_idx:end_idx], + len(self.random_symbols[const_idx]), ) - ) - symbols = np.asarray(symbols) + ] else: # Fixed modulation across all subcarriers const_name = np.random.choice(self.constellations) const = default_const_map[const_name] / np.mean( np.abs(default_const_map[const_name]) ) - symbol_nums = np.random.randint( - 0, len(const), int(2 * sym_mult * self.num_iq_samples) - ) + symbol_nums = np.random.randint(0, len(const), int(self.num_iq_samples)) symbols = const[symbol_nums] divisible_index = -(len(symbols) % num_subcarriers) if divisible_index != 0: @@ -742,7 +722,7 @@ def _generate_samples(self, item: Tuple) -> np.ndarray: flattened = cyclic_prefixed.T.flatten() # Generate randomized LPF cutoff = np.random.uniform(0.25, 0.475) - transition_bandwidth = (0.5-cutoff)/4 + transition_bandwidth = (0.5 - cutoff) / 4 num_taps = estimate_filter_length(transition_bandwidth) taps = sp.firwin( num_taps, @@ -750,7 +730,7 @@ def _generate_samples(self, item: Tuple) -> np.ndarray: width=transition_bandwidth, window=sp.get_window("blackman", num_taps), scale=True, - fs=1 + fs=1, ) # Apply random LPF output = torchsig_convolve(flattened, taps, gpu=self.use_gpu)[:-num_taps] @@ -824,7 +804,7 @@ def _generate_samples(self, item: Tuple) -> np.ndarray: ] # Randomize the start index (while bypassing the initial windowing if present) - if sym_mult == 1 and num_subcarriers * 4 * burst_dur < self.num_iq_samples: + if num_subcarriers * 4 * burst_dur < self.num_iq_samples: start_idx = np.random.randint(0, output.shape[0] - self.num_iq_samples) else: if original_on: @@ -951,7 +931,7 @@ def _generate_samples(self, item: Tuple) -> np.ndarray: symbol_nums = np.random.randint( 0, len(const_oversampled), - int(2 * self.num_iq_samples / samples_per_symbol_recalculated), + int(self.num_iq_samples / samples_per_symbol_recalculated), ) symbols = const_oversampled[symbol_nums] From 5088e72e5377859158f0497dcecd47f581c10350 Mon Sep 17 00:00:00 2001 From: Garrett Vanhoy Date: Mon, 22 May 2023 10:40:00 -0400 Subject: [PATCH 10/11] Adding initial Dockerfile (#108) --- Dockerfile | 15 +++++++++++++++ README.md | 11 +++++++++++ pyproject.toml | 2 +- 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..46586ba --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM pytorch/pytorch:1.13.1-cuda11.6-cudnn8-runtime + +ENV DEBIAN_FRONTEND=noninteractive + +ADD torchsig/ /build/torchsig + +ADD pyproject.toml /build/pyproject.toml + +RUN pip3 install /build + +RUN pip3 install notebook + +WORKDIR /workspace/code + +ADD examples/ /workspace/code/examples \ No newline at end of file diff --git a/README.md b/README.md index d1bd9ec..5ad4c45 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,17 @@ cd torchsig pip install . ``` +## Using the Dockerfile +If you have Docker installed along with compatible GPUs and drivers, you can try: + +``` +docker build -t torchsig -f Dockerfile . +docker run -d --rm --network=host --shm-size=32g --gpus all --name torchsig_workspace torchsig tail -f /dev/null +docker exec torchsig_workspace jupyter notebook --allow-root --ip=0.0.0.0 --no-browser +``` + +Then use the URL in the output in your browser to run the examples and notebooks. + ## License --- TorchSig is released under the MIT License. The MIT license is a popular open-source software license enabling free use, redistribution, and modifications, even for commercial purposes, provided the license is included in all copies or substantial portions of the software. TorchSig has no connection to MIT, other than through the use of this license. diff --git a/pyproject.toml b/pyproject.toml index 80f8178..4fca010 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ readme = "README.md" requires-python = ">=3.7" license = {text = "MIT"} dependencies = [ - "torch==1.13.0", + "torch==1.13.1", "torchvision", "tqdm", "numpy", From 0a738d4455cf13aa2f0823584af1d1dc29049ca5 Mon Sep 17 00:00:00 2001 From: Garrett Vanhoy Date: Mon, 22 May 2023 10:41:10 -0400 Subject: [PATCH 11/11] Incrementing version --- torchsig/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/torchsig/__init__.py b/torchsig/__init__.py index 493f741..260c070 100644 --- a/torchsig/__init__.py +++ b/torchsig/__init__.py @@ -1 +1 @@ -__version__ = "0.3.0" +__version__ = "0.3.1"