def param_sampler(params, probs=None): """Makes a generator to sample randomly from possible parameters. Parameters ---------- params : list of lists or list of float Possible parameter values. probs : list of float, optional Probabilities with which to sample each parameter option. If None, each parameter option is sampled uniformly. Yields ------ obj A randomly sampled element from params. """ # If input is a list of lists, check each element, and flatten if needed if isinstance(params[0], list): params = [check_flat(lst) for lst in params] # In order to use numpy's choice, with probabilities, choices are made on indices # This is because the params can be a messy-sized list, that numpy choice does not like inds = np.array(range(len(params))) # While loop allows the generator to be called an arbitrary number of times. while True: yield params[np.random.choice(inds, p=probs)]
def param_jitter(params, jitters): """Create a generator that adds jitter to parameter definitions. Parameters ---------- params : list of lists or list of float Possible parameter values. jitters : list of lists or list of float The scale of the jitter for each parameter. Must be the same shape and organization as `params`. Yields ------ list of float A jittered set of parameters. Notes ----- - Jitter is added as random samples from a normal (gaussian) distribution. - The jitter specified corresponds to the standard deviation of the normal distribution. - For any parameter for which there should be no jitter, set the corresponding value to zero. Examples -------- Jitter aperiodic definitions, for offset and exponent, each with the same amount of jitter: >>> aps = param_jitter([1, 1], [0.1, 0.1]) Jitter center frequency of peak definitions, by different amounts for alpha & beta: >>> peaks = param_jitter([[10, 1, 1], [20, 1, 1]], [[0.1, 0, 0], [0.5, 0, 0]]) """ # Check if inputs are list of lists, and flatten if so if isinstance(params[0], list): params = check_flat(params) jitters = check_flat(jitters) # While loop allows the generator to be called an arbitrary number of times while True: out_params = [None] * len(params) for ind, (param, jitter) in enumerate(zip(params, jitters)): out_params[ind] = param + np.random.normal(0, jitter) yield out_params
def gen_power_spectrum(freq_range, aperiodic_params, gauss_params, nlv=0.005, freq_res=0.5): """Generate a synthetic power spectrum. Parameters ---------- freq_range : list of [float, float] Minimum and maximum values of the desired frequency vector. aperiodic_params : list of float Parameters to create the aperiodic component of a power spectrum. Length of 2 or 3 (see note). gauss_params : list of float or list of list of float Parameters to create peaks. Total length of n_peaks * 3 (see note). nlv : float, optional Noise level to add to generated power spectrum. Default: 0.005 freq_res : float, optional Frequency resolution for the synthetic power spectra. Default: 0.5 Returns ------- freqs : 1d array Frequency values, in linear space. powers : 1d array Power values, in linear space. Notes ----- Aperiodic Parameters: - The function for the aperiodic process to use is inferred from the provided parameters. - If length of 2, the 'fixed' aperiodic mode is used, if length of 3, 'knee' is used. Gaussian Parameters: - Each gaussian description is a set of three values: - mean (Center Frequency), amplitude (Amplitude), and std (Bandwidth) - Make sure any center frequencies you request are within the simulated frequency range - The total number of parameters that need to be specified is number of peaks * 3 - These can be specified in as all together in a flat list. - For example: [10, 1, 1, 20, 0.5, 1] - They can also be grouped into a list of lists - For example: [[10, 1, 1], [20, 0.5, 1]] Examples -------- Generate a power spectrum with a single >>> freqs, psd = gen_power_spectrum([1, 50], [0, 2], [10, 1, 1]) Generate a power spectrum with alpha and beta peaks >>> freqs, psd = gen_power_spectrum([1, 50], [0, 2], [[10, 1, 1], [20, 0.5, 1]]) """ freqs = gen_freqs(freq_range, freq_res) powers = gen_power_vals(freqs, aperiodic_params, check_flat(gauss_params), nlv) return freqs, powers
def param_sampler(params, probs=None): """Create a generator to sample randomly from possible parameters. Parameters ---------- params : list of lists or list of float Possible parameter values. probs : list of float, optional Probabilities with which to sample each parameter option. If None, each parameter option is sampled uniformly. Yields ------ list of float A randomly sampled set of parameters. Examples -------- Sample from aperiodic definitions with high and low exponents, with 50% probability of each: >>> aps = param_sampler([[1, 1], [2, 1]], probs=[0.5, 0.5]) Sample from peak definitions of alpha or alpha & beta, with 75% change of sampling just alpha: >>> peaks = param_sampler([[10, 1, 1], [[10, 1, 1], [20, 0.5, 1]]], probs=[0.75, 0.25]) """ # If input is a list of lists, check each element, and flatten if needed if isinstance(params[0], list): params = [check_flat(lst) for lst in params] # In order to use numpy's choice, with probabilities, choices are made on indices # This is because the params can be a messy-sized list, that numpy choice does not like inds = np.array(range(len(params))) # Check that length of options is same as length of probs, if provided if np.any(probs): if len(inds) != len(probs): raise ValueError( "The number of options must match the number of probabilities." ) # While loop allows the generator to be called an arbitrary number of times while True: yield params[np.random.choice(inds, p=probs)]
def collect_sim_params(aperiodic_params, periodic_params, nlv): """Collect simulation parameters into a SimParams object. Parameters ---------- aperiodic_params : list of float Parameters of the aperiodic component of the power spectrum. periodic_params : list of float or list of list of float Parameters of the periodic component of the power spectrum. nlv : float Noise level of the power spectrum. Returns ------- SimParams Object containing the simulation parameters. """ return SimParams(aperiodic_params.copy(), sorted(group_three(check_flat(periodic_params))), nlv)
def gen_power_spectrum(freq_range, aperiodic_params, periodic_params, nlv=0.005, freq_res=0.5, f_rotation=None, return_params=False): """Generate a simulated power spectrum. Parameters ---------- freq_range : list of [float, float] Frequency range to simulate power spectrum across, as [f_low, f_high], inclusive. aperiodic_params : list of float Parameters to create the aperiodic component of a power spectrum. Length should be 2 or 3 (see note). periodic_params : list of float or list of list of float Parameters to create the periodic component of a power spectrum. Total length of n_peaks * 3 (see note). nlv : float, optional, default: 0.005 Noise level to add to generated power spectrum. freq_res : float, optional, default: 0.5 Frequency resolution for the simulated power spectrum. f_rotation : float, optional Frequency value, in Hz, to rotate around. Should only be set if spectrum is to be rotated. return_params : bool, optional, default: False Whether to return the parameters for the simulated spectrum. Returns ------- freqs : 1d array Frequency values, in linear spacing. powers : 1d array Power values, in linear spacing. sim_params : SimParams Definition of parameters used to create the spectrum. Only returned if `return_params` is True. Notes ----- Aperiodic Parameters: - The function for the aperiodic process to use is inferred from the provided parameters. - If length of 2, the 'fixed' aperiodic mode is used, if length of 3, 'knee' is used. Periodic Parameters: - The periodic component is comprised of a set of 'peaks', each of which is described as: * Mean (Center Frequency), height (Power), and standard deviation (Bandwidth). * Make sure any center frequencies you request are within the simulated frequency range. - The total number of parameters that need to be specified is number of peaks * 3 * These can be specified in as all together in a flat list (ex: [10, 1, 1, 20, 0.5, 1]) * They can also be grouped into a list of lists (ex: [[10, 1, 1], [20, 0.5, 1]]) Rotating Power Spectra: - You can optionally specify a rotation frequency, such that power spectra will be simulated and rotated around that point to the specified aperiodic exponent. * This can be used so that any power spectra simulated with the same 'f_rotation' will relate to each other by having the specified rotation point. - Note that rotating power spectra changes the offset. * If you specify an offset value to simulate as well as 'f_rotation', the returned spectrum will NOT have the requested offset. It instead will have the offset value required to create the requested aperiodic exponent with the requested rotation point. * If you return SimParams, the recorded offset will be the calculated offset of the data post rotation, and not the entered value. - You cannot rotate power spectra simulated with a knee. * The procedure we use to rotate does not support spectra with a knee, and so setting 'f_rotation' with a knee will lead to an error. Examples -------- Generate a power spectrum with a single peak, at 10 Hz: >>> freqs, powers = gen_power_spectrum([1, 50], [0, 2], [10, 0.5, 1]) Generate a power spectrum with alpha and beta peaks: >>> freqs, powers = gen_power_spectrum([1, 50], [0, 2], [[10, 0.5, 1], [20, 0.5, 1]]) Generate a power spectrum, that was rotated around a particular frequency point: >>> freqs, powers = gen_power_spectrum([1, 50], [None, 2], [10, 0.5, 1], f_rotation=15) """ freqs = gen_freqs(freq_range, freq_res) if f_rotation: powers = gen_rotated_power_vals(freqs, aperiodic_params, check_flat(periodic_params), nlv, f_rotation) # The rotation changes the offset, so recalculate it's value & update params new_offset = compute_rotation_offset(aperiodic_params[1], f_rotation) aperiodic_params = [new_offset, aperiodic_params[1]] else: powers = gen_power_vals(freqs, aperiodic_params, check_flat(periodic_params), nlv) if return_params: sim_params = collect_sim_params(aperiodic_params, periodic_params, nlv) return freqs, powers, sim_params else: return freqs, powers
def gen_group_power_spectra(n_spectra, freq_range, aperiodic_params, periodic_params, nlvs=0.005, freq_res=0.5, f_rotation=None, return_params=False): """Generate a group of simulated power spectra. Parameters ---------- n_spectra : int The number of power spectra to generate. freq_range : list of [float, float] Frequency range to simulate power spectra across, as [f_low, f_high], inclusive. aperiodic_params : list of float or generator Parameters for the aperiodic component of the power spectra. periodic_params : list of float or generator Parameters for the periodic component of the power spectra. Length of n_peaks * 3. nlvs : float or list of float or generator, optional, default: 0.005 Noise level to add to generated power spectrum. freq_res : float, optional, default: 0.5 Frequency resolution for the simulated power spectra. f_rotation : float, optional Frequency value, in Hz, to rotate around. Should only be set if spectra are to be rotated. return_params : bool, optional, default: False Whether to return the parameters for the simulated spectra. Returns ------- freqs : 1d array Frequency values, in linear spacing. powers : 2d array Matrix of power values, in linear spacing, as [n_power_spectra, n_freqs]. sim_params : list of SimParams Definitions of parameters used for each spectrum. Has length of n_spectra. Only returned if `return_params` is True. Notes ----- Parameters options can be: - A single set of parameters. If so, these same parameters are used for all spectra. - A list of parameters whose length is n_spectra. If so, each successive parameter set is such for each successive spectrum. - A generator object that returns parameters for a power spectrum. If so, each spectrum has parameters sampled from the generator. Aperiodic Parameters: - The function for the aperiodic process to use is inferred from the provided parameters. - If length of 2, the 'fixed' aperiodic mode is used, if length of 3, 'knee' is used. Periodic Parameters: - The periodic component is comprised of a set of 'peaks', each of which is described as: * Mean (Center Frequency), height (Power), and standard deviation (Bandwidth). * Make sure any center frequencies you request are within the simulated frequency range. Rotating Power Spectra: - You can optionally specify a rotation frequency, such that power spectra will be simulated and rotated around that point to the specified aperiodic exponent. * This can be used so that any power spectra simulated with the same 'f_rotation' will relate to each other by having the specified rotation point. - Note that rotating power spectra changes the offset. * If you specify an offset value to simulate as well as 'f_rotation', the returned spectrum will NOT have the requested offset. It instead will have the offset value required to create the requested aperiodic exponent with the requested rotation point. * If you return SimParams, the recorded offset will be the calculated offset of the data post rotation, and not the entered value. - You cannot rotate power spectra simulated with a knee. * The procedure we use to rotate does not support spectra with a knee, and so setting 'f_rotation' with a knee will lead to an error. Examples -------- Generate 2 power spectra using the same parameters: >>> freqs, powers = gen_group_power_spectra(2, [1, 50], [0, 2], [10, 0.5, 1]) Generate 10 power spectra, randomly sampling possible parameters: >>> from fooof.sim.params import param_sampler >>> ap_opts = param_sampler([[0, 1.0], [0, 1.5], [0, 2]]) >>> pe_opts = param_sampler([[], [10, 0.5, 1], [10, 0.5, 1, 20, 0.25, 1]]) >>> freqs, powers = gen_group_power_spectra(10, [1, 50], ap_opts, pe_opts) Generate 5 power spectra, rotated around 20 Hz: >>> ap_params = [[None, 1], [None, 1.25], [None, 1.5], [None, 1.75], [None, 2]] >>> pe_params = [10, 0.5, 1] >>> freqs, powers = gen_group_power_spectra(5, [1, 50], ap_params, pe_params, f_rotation=20) Generate power spectra stepping across exponent values, and return parameter values: >>> from fooof.sim.params import Stepper, param_iter >>> ap_params = param_iter([0, Stepper(1, 2, 0.25)]) >>> pe_params = [10, 0.5, 1] >>> freqs, powers, sps = gen_group_power_spectra(5, [1, 50], ap_params, pe_params, ... return_params=True) """ # Initialize things freqs = gen_freqs(freq_range, freq_res) powers = np.zeros([n_spectra, len(freqs)]) sim_params = [None] * n_spectra # Check if inputs are generators, if not, make them into repeat generators ap_params = check_iter(aperiodic_params, n_spectra) pe_params = check_iter(periodic_params, n_spectra) nlvs = check_iter(nlvs, n_spectra) f_rots = check_iter(f_rotation, n_spectra) # Simulate power spectra for ind, ap, pe, nlv, f_rot in zip(range(n_spectra), ap_params, pe_params, nlvs, f_rots): if f_rotation: powers[ind, :] = gen_rotated_power_vals(freqs, ap, check_flat(pe), nlv, f_rot) aperiodic_params = [compute_rotation_offset(ap[1], f_rot), ap[1]] else: powers[ind, :] = gen_power_vals(freqs, ap, check_flat(pe), nlv) sim_params[ind] = collect_sim_params(ap, pe, nlv) if return_params: return freqs, powers, sim_params else: return freqs, powers