From 376f7e175445450ccabaa638527300d99fb5425c Mon Sep 17 00:00:00 2001 From: ryanhammonds Date: Fri, 29 Jan 2021 14:23:21 -0800 Subject: [PATCH 1/6] incomplete tile warning --- neurodsp/sim/periodic.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/neurodsp/sim/periodic.py b/neurodsp/sim/periodic.py index 92e7d2d5..01112115 100644 --- a/neurodsp/sim/periodic.py +++ b/neurodsp/sim/periodic.py @@ -1,5 +1,6 @@ """Simulating time series, with periodic activity.""" +import warnings from itertools import repeat import numpy as np @@ -50,6 +51,9 @@ def sim_oscillation(n_seconds, fs, freq, cycle='sine', phase=0, **cycle_params): ... cycle='asine', phase=0.5, rdsym=0.75) """ + # Check if tiling can produce full oscillations + _check_tiling(fs, freq) + # Figure out how many cycles are needed for the signal n_cycles = int(np.ceil(n_seconds * freq)) @@ -150,6 +154,9 @@ def sim_bursty_oscillation(n_seconds, fs, freq, burst_def='prob', burst_params={ if burst_def == 'prob' and burst_param not in burst_params: burst_params[burst_param] = temp + # Check if tiling can produce full oscillations + _check_tiling(fs, freq) + # Simulate a normalized cycle to use for bursts n_seconds_cycle = 1/freq osc_cycle = sim_normalized_cycle(n_seconds_cycle, fs, cycle, **cycle_params) @@ -303,3 +310,15 @@ def get_burst_samples(is_oscillating, fs, freq): bursts = np.repeat(is_oscillating, n_samples_cycle) return bursts + + +def _check_tiling(fs, freq): + """Check if tiling can produce full oscillations.""" + + if not (fs/freq).is_integer(): + + warnings.warn(''' + The requested frequency and sampling rate are not evenly divisible, resulting + in an artifactual spectral slope. Consider updating freq and fs to an integer + divisor/multiple pair. + ''', category=RuntimeWarning) From 08b0158129e51dafc040ac05a569af23da5e2334 Mon Sep 17 00:00:00 2001 From: ryanhammonds Date: Fri, 5 Feb 2021 15:20:44 -0800 Subject: [PATCH 2/6] review update --- neurodsp/sim/periodic.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/neurodsp/sim/periodic.py b/neurodsp/sim/periodic.py index 01112115..26e627a7 100644 --- a/neurodsp/sim/periodic.py +++ b/neurodsp/sim/periodic.py @@ -313,12 +313,12 @@ def get_burst_samples(is_oscillating, fs, freq): def _check_tiling(fs, freq): - """Check if tiling can produce full oscillations.""" + """Check if tiling will produce an integer number of cycles for the simulated oscillation.""" if not (fs/freq).is_integer(): warnings.warn(''' - The requested frequency and sampling rate are not evenly divisible, resulting - in an artifactual spectral slope. Consider updating freq and fs to an integer - divisor/multiple pair. + The settings for the frequency and sampling rate are not evenly divisible. In the frequency + domain, this can lead to power in non-simulated frequencies in the power spectrum. Consider + updating freq and fs to an integer divisor/multiple pair." ''', category=RuntimeWarning) From 8a6de1e961da0748a7c61e8b769823a827da9d61 Mon Sep 17 00:00:00 2001 From: ryanhammonds Date: Fri, 5 Feb 2021 15:54:46 -0800 Subject: [PATCH 3/6] move warning to note --- neurodsp/sim/periodic.py | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/neurodsp/sim/periodic.py b/neurodsp/sim/periodic.py index 26e627a7..251ef04f 100644 --- a/neurodsp/sim/periodic.py +++ b/neurodsp/sim/periodic.py @@ -1,6 +1,5 @@ """Simulating time series, with periodic activity.""" -import warnings from itertools import repeat import numpy as np @@ -39,6 +38,13 @@ def sim_oscillation(n_seconds, fs, freq, cycle='sine', phase=0, **cycle_params): sig : 1d array Simulated oscillation. + Notes + ----- + When ``freq`` and ``fs`` are not evenly divisible, there will be a non-integer number of samples + per cycle. In the frequency domain, this will lead to power in non-simulated frequencies. + Consider updating ``freq and ``fs`` to an integer divisor/multiple when investigating spectral + properties. + Examples -------- Simulate a continuous sinusoidal oscillation at 5 Hz: @@ -51,9 +57,6 @@ def sim_oscillation(n_seconds, fs, freq, cycle='sine', phase=0, **cycle_params): ... cycle='asine', phase=0.5, rdsym=0.75) """ - # Check if tiling can produce full oscillations - _check_tiling(fs, freq) - # Figure out how many cycles are needed for the signal n_cycles = int(np.ceil(n_seconds * freq)) @@ -154,9 +157,6 @@ def sim_bursty_oscillation(n_seconds, fs, freq, burst_def='prob', burst_params={ if burst_def == 'prob' and burst_param not in burst_params: burst_params[burst_param] = temp - # Check if tiling can produce full oscillations - _check_tiling(fs, freq) - # Simulate a normalized cycle to use for bursts n_seconds_cycle = 1/freq osc_cycle = sim_normalized_cycle(n_seconds_cycle, fs, cycle, **cycle_params) @@ -310,15 +310,3 @@ def get_burst_samples(is_oscillating, fs, freq): bursts = np.repeat(is_oscillating, n_samples_cycle) return bursts - - -def _check_tiling(fs, freq): - """Check if tiling will produce an integer number of cycles for the simulated oscillation.""" - - if not (fs/freq).is_integer(): - - warnings.warn(''' - The settings for the frequency and sampling rate are not evenly divisible. In the frequency - domain, this can lead to power in non-simulated frequencies in the power spectrum. Consider - updating freq and fs to an integer divisor/multiple pair." - ''', category=RuntimeWarning) From d7325fc523097fdca67cd1aa367aaddb4811d813 Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Mon, 8 Feb 2021 18:46:22 -0500 Subject: [PATCH 4/6] update note text --- neurodsp/sim/periodic.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/neurodsp/sim/periodic.py b/neurodsp/sim/periodic.py index 251ef04f..c94a51ed 100644 --- a/neurodsp/sim/periodic.py +++ b/neurodsp/sim/periodic.py @@ -40,10 +40,10 @@ def sim_oscillation(n_seconds, fs, freq, cycle='sine', phase=0, **cycle_params): Notes ----- - When ``freq`` and ``fs`` are not evenly divisible, there will be a non-integer number of samples - per cycle. In the frequency domain, this will lead to power in non-simulated frequencies. - Consider updating ``freq and ``fs`` to an integer divisor/multiple when investigating spectral - properties. + Depending on the requested frequency (`freq`), sampling rate (`fs`), and signal + length (`n_seconds`), the simulated signal may have a non-integer number of cycles. + If so, the frequency-domain representation of the signal will have some power in non-simulated + frequencies. To avoid this, choose `n_seconds` and `fs` to create an integer number of cycles. Examples -------- From 68f79cdbdb4a12eb9ed9ef9e3c6f69e9ece33763 Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Mon, 8 Feb 2021 18:52:02 -0500 Subject: [PATCH 5/6] add util func to check osc_def --- neurodsp/sim/utils.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 neurodsp/sim/utils.py diff --git a/neurodsp/sim/utils.py b/neurodsp/sim/utils.py new file mode 100644 index 00000000..c422bae7 --- /dev/null +++ b/neurodsp/sim/utils.py @@ -0,0 +1,30 @@ +"""Utility functions for simulations.""" + +################################################################################################### +################################################################################################### + +def check_osc_def(n_seconds, fs, freq): + """Check whether a requested oscillation definition will have an integer number of cycles. + + Parameters + ---------- + n_seconds : float + Simulation time, in seconds. + fs : float + Signal sampling rate, in Hz. + freq : float + Oscillation frequency. + + Returns + ------- + bool + Whether the definition will have an integer number of cycles. + """ + + # Sampling rate check: check if the number of samples per cycle is an integer + srate_check = (fs/freq).is_integer() + + # Time check: check if signal length matches an integer number of cycles + time_check = (n_seconds * freq).is_integer() + + return srate_check and time_check From 5f5d9dbc89520cde85d9dd7e7e496c3ed0a17c17 Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Mon, 8 Feb 2021 18:58:14 -0500 Subject: [PATCH 6/6] add tests for check_osc_def --- neurodsp/tests/sim/test_utils.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 neurodsp/tests/sim/test_utils.py diff --git a/neurodsp/tests/sim/test_utils.py b/neurodsp/tests/sim/test_utils.py new file mode 100644 index 00000000..bf04871b --- /dev/null +++ b/neurodsp/tests/sim/test_utils.py @@ -0,0 +1,24 @@ +"""Tests for neurodsp.sim.utils.""" + +from neurodsp.sim.utils import * + +################################################################################################### +################################################################################################### + +def test_check_osc_def(): + + n_seconds = 1.0 + fs = 100 + freq = 10 + + # Check definition that should pass + assert check_osc_def(n_seconds, fs, freq) + + # Check a definition that should fail the sampling rate check + assert not check_osc_def(1.05, fs, freq) + + # Check a definition that should fail the time check + assert not check_osc_def(n_seconds, 1111, freq) + + # Check a definition that should fail both checks + assert not check_osc_def(1.05, 1111, freq)