Phase-Amplitude Coupling#

The comodulogram is a matrix representing the strength of the coupling between the phase of driver frequencies and the amplitude of signal frequencies. The ‘pac’ method allows to extract pairs of driver/signal frequencies that exhibit higher scores of coupling

from biotuner.biotuner_object import compute_biotuner
import numpy as np
# load data
data = np.load('../data/EEG_example.npy')

biotuning = compute_biotuner(1000, data = data[10], peaks_function = 'harmonic_recurrence', precision = 0.1, n_harm = 10,
                    ratios_n_harms = 5, ratios_inc_fit = False, ratios_inc = True, scale_cons_limit = 0.1) # Initialize biotuner object
pac_freqs, _ = biotuning.pac(n_values = 30, plot=True, drive_precision = 0.1, max_drive_freq = 7, method = 'duprelatour')
print(pac_freqs)
[[3.8, 27.0], [6.0, 33.0], [3.8, 28.0], [6.5, 36.0], [5.2, 32.0], [6.5, 35.0], [5.4, 29.0], [3.2, 43.0], [6.300000000000001, 28.0], [5.2, 33.0], [5.4, 8.0], [3.8, 29.0], [5.4, 9.0], [5.4, 46.0], [5.4, 45.0], [5.4, 44.0], [5.4, 10.0], [5.4, 11.0], [3.0, 36.0], [5.4, 12.0], [3.1, 40.0], [3.1, 29.0], [6.4, 30.0], [3.6, 28.0], [3.5, 44.0], [3.5, 45.0], [6.4, 29.0], [6.300000000000001, 29.0], [3.0, 27.0], [6.5, 34.0]]
../../_images/phase_amplitude_coupling_1_1.png

Different methods can be used to compute the PAC: methods = [‘ozkurt’, ‘canolty’, ‘tort’, ‘penny’, ‘vanwijk’, ‘duprelatour’, ‘colgin’, ‘sigl’, ‘bispectrum’]

pac_freqs, _ = biotuning.pac(plot=True, drive_precision = 0.1, n_values = 10, max_drive_freq = 7, method = 'ozkurt')
pac_freqs
[[3.1, 44.0],
 [3.1, 34.0],
 [3.1, 35.0],
 [3.4, 46.0],
 [3.4, 19.0],
 [3.4, 15.0],
 [3.4, 14.0],
 [3.1, 42.0],
 [3.4, 23.0],
 [3.1, 18.0]]
../../_images/phase_amplitude_coupling_3_1.png

Deriving tunings from PAC information#

By computing the most frequent phase and amplitude frequencies from the ‘pac_freqs’ lists, we can derive a series of ratios by coupling each phase with each amplitude frequencies

from biotuner.biotuner_utils import pairs_most_frequent
pac_frequent = pairs_most_frequent(pac_freqs, 3)
pac_frequent
[[18.0, 23.0, 42.0], [3.4, 3.1]]
from biotuner.biotuner_utils import rebound
ratios = []
for i in range(len(pac_frequent[0])):
    for j in range(len(pac_frequent[1])):
        ratios.append(rebound(pac_frequent[0][i]/pac_frequent[1][j]))
        
ratios = sorted(ratios)
ratios
[1.3235294117647058,
 1.4516129032258065,
 1.5441176470588236,
 1.6911764705882353,
 1.6935483870967742,
 1.8548387096774193]

Another approach to derive tuning based on the information of the Phase-Amplitude Coupling would be to compute the ratios of each pairs of phase/amplitude frequencies, and then to apply the ‘scale_reduction’ function to extract the most consonant intervals. This is what the pac_mode function does.

from biotuner.scale_construction import pac_mode
from biotuner.metrics import dyad_similarity
pac_mode(pac_freqs, n=6, function=dyad_similarity)
c:\Users\User\anaconda3\envs\biotuner\lib\site-packages\numpy\lib\function_base.py:380: RuntimeWarning: Mean of empty slice.
  avg = a.mean(axis)
c:\Users\User\anaconda3\envs\biotuner\lib\site-packages\numpy\core\_methods.py:189: RuntimeWarning: invalid value encountered in double_scalars
  ret = ret.dtype.type(ret / rcount)
[1.0294117647058825,
 1.3709677419354838,
 1.411290322580645,
 1.4516129032258065,
 1.6935483870967742,
 1.7741935483870968]
pac_mode(pac_freqs, n=10)
[1.0294117647058825,
 1.1029411764705883,
 1.3709677419354838,
 1.397058823529412,
 1.411290322580645,
 1.4516129032258065,
 1.6911764705882353,
 1.6911764705882353,
 1.6935483870967742,
 1.7741935483870968]

Using coupled frequencies as generator interval#

This code analyzes phase amplitude coupling (PAC) and constructs a musical scale based on the frequency ratios associated with the PAC.

First, the code calculates the frequency ratio between the two oscillatory signals that exhibit PAC, stored in the pac_freqs variable. The frequency ratio is then converted to a rational number using the sp.Rational() function and its denominator is limited to 1000 using .limit_denominator().

Next, the code generates the Stern-Brocot interval associated with the rational number using gen_interval_to_stern_brocot(). The interval represents a sequence of fractions that converge to the rational number and is used to determine the generator interval tuning. A generator interval is a specific type of musical interval that can be used to generate a scale.

The number of steps in the interval is limited to 16 using .limit_denominator(), and the generator interval tuning is calculated based on the interval and number of steps using generator_interval_tuning(). The resulting gen_int_tuning variable contains the musical scale based on the PAC frequency ratio, with limit_steps number of steps in the scale and an octave of 2. The tuning is generated by sorting the generator interval tuning in ascending order using sorted().

# Import the necessary functions from the biotuner.scale_construction module
from biotuner.scale_construction import gen_interval_to_stern_brocot, generator_interval_tuning
import sympy as sp
from fractions import Fraction

# Calculate the frequency ratio between the two oscillatory signals that exhibit phase amplitude coupling (stored in pac_freqs variable) and convert it to a rational number with a denominator limited to 1000
ratio = rebound(pac_freqs[0][1]/pac_freqs[0][0])
print(sp.Rational(ratio).limit_denominator(1000))

# Set the limit on the number of steps in the interval to 16 and generate the Stern-Brocot interval based on the frequency ratio
limit_steps = 16
stern_brocot_ratio = gen_interval_to_stern_brocot(ratio)

# Limit the number of steps in the interval to 16 and calculate the steps needed for the generator interval tuning
steps = Fraction(stern_brocot_ratio).limit_denominator(16).denominator

# Calculate the generator interval tuning based on the interval and number of steps, and sort the tuning in ascending order
gen_int_tuning = sorted(generator_interval_tuning(interval = ratio, steps = steps, octave = 2))

# Store the resulting musical scale in the gen_int_tuning variable
gen_int_tuning
55/31
[[1.0,
  1.0987148531753526,
  1.2385512890340338,
  1.3961850894565473,
  1.5738813735691988,
  1.7741935483870968],
 [1.0,
  1.7741935483870968,
  1.5738813735691988,
  1.3961850894565473,
  1.2385512890340338,
  1.0987148531753526]]

Deriving euclidian rhythms from PAC information#

This code constructs a musical scale based on the phase amplitude coupling (PAC) frequencies and uses it to generate a consonant rhythmic pattern. The PAC scale is generated using the pac_mode() function and the consonant rhythmic pattern is generated using the consonant_euclid() function. The resulting rhythm is represented as a list of strings and can be played using the Euclidean rhythm. This code could be useful for creating music that is based on the natural rhythms of biological systems

# Import the necessary dictionaries and functions from the biotuner module
from biotuner.dictionaries import *
from biotuner.rhythm_construction import *

# Generate a musical scale based on the phase amplitude coupling (PAC) frequencies using the pac_mode() function
pac_scale = pac_mode(pac_freqs, 10, function=dyad_similarity)

# Generate a consonant Euclidean rhythm based on the PAC scale using the consonant_euclid() function
euclid_final, cons = consonant_euclid(pac_scale, n_steps_down = 3, limit_denom = 4, 
                                      limit_cons =1, limit_denom_final = 100)

# Generate a list of interval vectors based on the Euclidean rhythm
interval_vectors = [interval_vector(x) for x in euclid_final]

# Convert the list of interval vectors to a list of strings using the interval_vec_to_string() function
strings = interval_vec_to_string(interval_vectors)

# Convert the rhythmic pattern strings to their corresponding reference rhythms based on the dict_rhythms dictionary using the euclid_string_to_referent() function
euclid_referent = euclid_string_to_referent(strings, dict_rhythms)

# Store the resulting Euclidean rhythm, interval vectors, and reference rhythms in the euclid_final, interval_vectors, and euclid_referent variables, respectively
euclid_final, interval_vectors, euclid_referent
c:\Users\User\anaconda3\envs\biotuner\lib\fractions.py:612: RuntimeWarning: overflow encountered in longlong_scalars
  self._denominator * other.numerator)
([[1, 0, 0],
  [1, 0, 0, 0],
  [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
  [1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0],
  [1, 1, 1, 0],
  [1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0]],
 [[3], [4], [4, 4, 4], [3, 3, 3, 3], [1, 1, 2], [1, 1, 2, 1, 1, 2, 1, 1, 2]],
 ['None',
  'None',
  'None',
  'It is periodic with four repetitions of E(1,3) = [100]. It is the (12/8)-time Fandago clapping pattern in the Flamenco music of southern Spain, where 1 denotes a loud clap and 0 soft clap.',
  'It is the archetypal pattern of the Cumbia from Colombia, as well as a Calypso rhythm from Trinidad. It is also a thirteenth century Persian rhythm called Khalif-e-saghil, as well as the trochoid choreic rhythmic pattern of ancient Greece.',
  'None'])