# Notebook from Krishna00111/Machine-Learning-for-Healthcare-Analytics-Projects
Path: Chapter03/chapter3.ipynb

# Classifying DNA Sequences
### Presented by Eduonix

During this tutorial, we will explore the world of bioinformatics by using Markov models, K-nearest neighbor (KNN) algorithms, support vector machines, and other common classifiers to classify short E. Coli DNA sequences. This project will use a dataset from the UCI Machine Learning Repository that has 106 DNA sequences, with 57 sequential nucleotides (“base-pairs”) each.

You will learn how to:
* Import data from the UCI repository
* Convert text inputs to numerical data
* Build and train classification algorithms
* Compare and contrast classification algorithms

## Step 1: Importing the Dataset

The following code cells will import necessary libraries and import the dataset from the UCI repository as a Pandas DataFrame._____no_output_____

# To make sure all of the correct libraries are installed, import each module and print the version number

import sys
import numpy
import sklearn
import pandas

print('Python: {}'.format(sys.version))
print('Numpy: {}'.format(numpy.__version__))
print('Sklearn: {}'.format(sklearn.__version__))
print('Pandas: {}'.format(pandas.__version__))Python: 2.7.13 |Continuum Analytics, Inc.| (default, May 11 2017, 13:17:26) [MSC v.1500 64 bit (AMD64)]
Numpy: 1.14.0
Sklearn: 0.19.1
Pandas: 0.21.0
# Import, change module names
import numpy as np
import pandas as pd

# import the uci Molecular Biology (Promoter Gene Sequences) Data Set
url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/molecular-biology/promoter-gene-sequences/promoters.data'
names = ['Class', 'id', 'Sequence']
data = pd.read_csv(url, names = names)_____no_output_____print(data.iloc[0])Class                                                       +
id                                                        S10
Sequence    \t\ttactagcaatacgcttgcgttcggtggttaagtatgtataat...
Name: 0, dtype: object

## Step 2: Preprocessing the Dataset

The data is not in a usable form; as a result, we will need to process it before using it to train our algorithms._____no_output_____

# Building our Dataset by creating a custom Pandas DataFrame
# Each column in a DataFrame is called a Series. Lets start by making a series for each column.

classes = data.loc[:, 'Class']
print(classes[:5])0    +
1    +
2    +
3    +
4    +
Name: Class, dtype: object
# generate list of DNA sequences
sequences = list(data.loc[:, 'Sequence'])
dataset = {}

# loop through sequences and split into individual nucleotides
for i, seq in enumerate(sequences):

    # split into nucleotides, remove tab characters
    nucleotides = list(seq)
    nucleotides = [x for x in nucleotides if x != '\t']

    # append class assignment
    nucleotides.append(classes[i])

    # add to dataset
    dataset[i] = nucleotides

print(dataset[0])['t', 'a', 'c', 't', 'a', 'g', 'c', 'a', 'a', 't', 'a', 'c', 'g', 'c', 't', 't', 'g', 'c', 'g', 't', 't', 'c', 'g', 'g', 't', 'g', 'g', 't', 't', 'a', 'a', 'g', 't', 'a', 't', 'g', 't', 'a', 't', 'a', 'a', 't', 'g', 'c', 'g', 'c', 'g', 'g', 'g', 'c', 't', 't', 'g', 't', 'c', 'g', 't', '+']
# turn dataset into pandas DataFrame
dframe = pd.DataFrame(dataset)
print(dframe)   0   1   2   3   4   5   6   7   8   9   ... 96  97  98  99  100 101 102  \
0    t   t   g   a   t   a   c   t   c   t ...   c   c   t   a   g   c   g
1    a   g   t   a   c   g   a   t   g   t ...   c   g   a   g   a   c   t
2    c   c   a   t   g   g   g   t   a   t ...   g   c   t   a   g   t   a
3    t   t   c   t   a   g   g   c   c   t ...   a   t   g   g   a   c   t
4    a   a   t   g   t   g   g   t   t   a ...   g   a   a   g   g   a   t
5    g   t   a   t   a   c   g   a   t   a ...   t   g   c   g   c   a   c
6    c   c   g   g   a   a   g   c   a   a ...   a   g   c   t   a   t   t
7    a   c   a   a   t   a   t   a   a   t ...   g   a   g   g   t   g   c
8    a   t   g   t   t   g   g   a   t   t ...   a   c   a   t   g   g   a
9    t   g   a   g   a   g   g   a   a   t ...   c   t   a   a   t   c   a
10   a   a   a   t   a   a   a   a   t   c ...   c   t   c   c   c   c   c
11   c   c   c   g   c   g   g   c   a   c ...   c   t   g   t   a   t   a
12   g   a   t   t   t   g   g   a   c   t ...   t   c   a   c   g   c   a
13   c   g   a   a   a   a   a   c   t   c ...   t   t   g   c   c   t   g
14   t   t   g   t   t   t   t   t   g   t ...   a   t   t   a   c   a   a
15   t   t   t   c   t   g   t   t   c   t ...   g   g   c   a   t   a   t
16   g   g   g   g   g   g   t   g   g   g ...   a   t   a   g   c   a   t
17   c   t   c   a   a   a   a   a   a   t ...   g   t   a   a   g   c   a
18   g   c   a   a   c   a   a   t   c   c ...   a   g   t   a   a   g   a
19   t   a   t   g   g   a   g   a   a   a ...   g   a   c   g   c   g   c
20   t   c   t   t   a   g   c   c   g   g ...   c   t   a   a   a   g   c
21   c   g   a   g   a   a   c   t   g   g ...   a   t   g   g   a   t   g
22   g   c   g   t   a   g   a   g   a   c ...   t   t   a   g   c   c   a
23   g   t   c   g   a   g   t   t   c   c ...   g   t   c   a   t   t   c
24   t   g   t   t   g   t   c   a   g   g ...   t   c   c   a   t   t   a
25   g   a   t   t   c   t   t   t   t   g ...   c   c   g   g   g   g   g
26   g   t   a   g   t   g   c   g   c   a ...   a   a   c   a   c   a   a
27   t   t   t   c   g   c   c   a   c   a ...   g   t   t   t   a   g   t
28   t   g   t   g   a   c   t   g   g   t ...   c   g   t   g   t   g   t
29   a   g   t   g   a   g   g   c   t   a ...   c   c   t   a   a   g   c
30   a   t   t   a   a   t   a   a   t   a ...   t   g   g   g   a   g   a
31   g   g   t   g   a   a   t   t   c   c ...   c   g   a   g   a   t   a
32   t   t   t   t   c   t   g   a   t   t ...   g   t   c   c   t   t   t
33   a   c   t   a   c   a   a   c   g   c ...   a   g   t   t   g   t   c
34   t   g   g   g   a   a   c   a   t   c ...   c   t   c   a   c   t   t
35   g   t   t   a   c   a   g   g   g   c ...   a   t   t   g   t   t   c
36   t   t   t   t   t   g   c   t   t   t ...   a   t   g   a   t   t   g
37   a   a   a   g   a   a   a   a   a   a ...   c   t   g   c   t   g   t
38   t   c   t   t   g   a   t   t   a   t ...   t   g   t   g   c   c   g
39   a   a   c   t   a   a   a   a   a   a ...   t   c   a   t   t   t   g
40   a   a   a   a   a   c   g   a   t   a ...   g   g   t   c   t   g   a
41   t   t   t   g   t   t   t   t   c   t ...   c   c   t   t   g   a   t
42   g   c   g   a   g   a   c   t   g   g ...   a   a   a   c   t   a   g
43   c   t   c   a   c   g   a   g   c   c ...   t   a   c   t   a   a   g
44   g   a   t   t   g   a   g   c   a   g ...   a   t   t   g   g   g   a
45   c   a   a   a   c   g   c   t   a   c ...   a   g   g   c   a   g   c
46   g   c   a   c   c   t   c   t   t   c ...   a   t   t   a   c   a   g
47   g   g   c   t   t   c   c   c   g   a ...   t   t   g   t   g   g   t
48   g   c   c   a   c   c   a   a   a   c ...   g   a   a   g   t   g   t
49   c   a   a   a   c   g   t   a   a   c ...   c   a   a   g   g   a   c
50   t   t   c   c   g   t   c   c   a   a ...   t   t   c   a   c   a   a
51   t   c   c   a   t   t   a   a   t   c ...   t   c   a   g   c   c   a
52   g   g   c   a   g   t   t   g   g   t ...   t   g   t   t   c   t   c
53   t   c   g   a   g   a   g   a   g   g ...   c   c   t   a   t   a   a
54   c   c   g   c   t   g   a   a   t   a ...   t   t   a   t   a   t   t
55   g   a   c   t   a   g   a   c   t   c ...   t   t   t   g   c   a   t
56   t   a   g   c   g   t   t   a   t   a ...   g   t   t   a   g   t   g
57   +   +   +   +   +   +   +   +   +   + ...   -   -   -   -   -   -   -

   103 104 105
0    c   c   t
1    g   t   a
2    c   c   a
3    g   g   c
4    a   t   a
5    c   c   t
6    t   c   t
7    a   t   a
8    c   c   a
9    g   a   t
10   a   a   a
11   t   t   a
12   g   g   a
13   a   g   t
14   g   c   a
15   a   c   a
16   t   t   g
17   g   c   g
18   c   t   a
19   c   a   g
20   t   a   g
21   g   a   c
22   a   c   t
23   g   g   c
24   t   g   t
25   g   g   a
26   c   t   a
27   t   c   t
28   t   t   g
29   c   t   g
30   c   g   c
31   g   a   a
32   t   g   c
33   t   g   t
34   a   g   c
35   c   g   a
36   t   t   t
37   g   t   t
38   g   t   a
39   a   t   g
40   t   t   c
41   t   t   c
42   g   g   a
43   t   c   a
44   c   t   t
45   a   g   c
46   c   a   a
47   c   a   a
48   a   a   t
49   a   g   c
50   g   g   a
51   g   a   a
52   c   g   g
53   t   g   a
54   t   a   a
55   c   a   c
56   c   c   t
57   -   -   -

[58 rows x 106 columns]
# transpose the DataFrame
df = dframe.transpose()
print(df.iloc[:5])  0  1  2  3  4  5  6  7  8  9  ... 48 49 50 51 52 53 54 55 56 57
0  t  a  c  t  a  g  c  a  a  t ...  g  c  t  t  g  t  c  g  t  +
1  t  g  c  t  a  t  c  c  t  g ...  c  a  t  c  g  c  c  a  a  +
2  g  t  a  c  t  a  g  a  g  a ...  c  a  c  c  c  g  g  c  g  +
3  a  a  t  t  g  t  g  a  t  g ...  a  a  c  a  a  a  c  t  c  +
4  t  c  g  a  t  a  a  t  t  a ...  c  c  g  t  g  g  t  a  g  +

[5 rows x 58 columns]
# for clarity, lets rename the last dataframe column to class
df.rename(columns = {57: 'Class'}, inplace = True)
print(df.iloc[:5])   0  1  2  3  4  5  6  7  8  9  ...  48 49 50 51 52 53 54 55 56 Class
0  t  a  c  t  a  g  c  a  a  t  ...   g  c  t  t  g  t  c  g  t     +
1  t  g  c  t  a  t  c  c  t  g  ...   c  a  t  c  g  c  c  a  a     +
2  g  t  a  c  t  a  g  a  g  a  ...   c  a  c  c  c  g  g  c  g     +
3  a  a  t  t  g  t  g  a  t  g  ...   a  a  c  a  a  a  c  t  c     +
4  t  c  g  a  t  a  a  t  t  a  ...   c  c  g  t  g  g  t  a  g     +

[5 rows x 58 columns]
# looks good! Let's start to familiarize ourselves with the dataset so we can pick the most suitable
# algorithms for this data

df.describe()_____no_output_____# desribe does not tell us enough information since the attributes are text. Lets record value counts for each sequence
series = []
for name in df.columns:
    series.append(df[name].value_counts())

info = pd.DataFrame(series)
details = info.transpose()
print(details)      0     1     2     3     4     5     6     7     8     9  ...      48  \
+   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN  ...     NaN
-   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN  ...     NaN
a  26.0  34.0  30.0  22.0  36.0  42.0  38.0  34.0  33.0  36.0  ...    23.0
c  27.0  22.0  21.0  30.0  19.0  18.0  21.0  20.0  22.0  22.0  ...    36.0
g  15.0  24.0  28.0  28.0  29.0  22.0  17.0  20.0  19.0  20.0  ...    26.0
t  38.0  26.0  27.0  26.0  22.0  24.0  30.0  32.0  32.0  28.0  ...    21.0

     49    50    51    52    53    54    55    56  Class
+   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   53.0
-   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   53.0
a  24.0  28.0  27.0  25.0  22.0  26.0  24.0  27.0    NaN
c  42.0  31.0  32.0  21.0  32.0  29.0  29.0  17.0    NaN
g  18.0  24.0  14.0  25.0  22.0  28.0  24.0  28.0    NaN
t  22.0  23.0  33.0  35.0  30.0  23.0  29.0  34.0    NaN

[6 rows x 58 columns]
# Unfortunately, we can't run machine learning algorithms on the data in 'String' formats. As a result, we need to switch
# it to numerical data. This can easily be accomplished using the pd.get_dummies() function
numerical_df = pd.get_dummies(df)
numerical_df.iloc[:5]_____no_output_____# We don't need both class columns.  Lets drop one then rename the other to simply 'Class'.
df = numerical_df.drop(columns=['Class_-'])

df.rename(columns = {'Class_+': 'Class'}, inplace = True)
print(df.iloc[:5])   0_a  0_c  0_g  0_t  1_a  1_c  1_g  1_t  2_a  2_c  ...    54_t  55_a  55_c  \
0    0    0    0    1    1    0    0    0    0    1  ...       0     0     0
1    0    0    0    1    0    0    1    0    0    1  ...       0     1     0
2    0    0    1    0    0    0    0    1    1    0  ...       0     0     1
3    1    0    0    0    1    0    0    0    0    0  ...       0     0     0
4    0    0    0    1    0    1    0    0    0    0  ...       1     1     0

   55_g  55_t  56_a  56_c  56_g  56_t  Class
0     1     0     0     0     0     1      1
1     0     0     1     0     0     0      1
2     0     0     0     0     1     0      1
3     0     1     0     1     0     0      1
4     0     0     0     0     1     0      1

[5 rows x 229 columns]
# Use the model_selection module to separate training and testing datasets
from sklearn import model_selection

# Create X and Y datasets for training
X = np.array(df.drop(['Class'], 1))
y = np.array(df['Class'])

# define seed for reproducibility
seed = 1

# split data into training and testing datasets
X_train, X_test, y_train, y_test = model_selection.train_test_split(X, y, test_size=0.25, random_state=seed)
_____no_output_____

## Step 3: Training and Testing the Classification Algorithms

Now that we have preprocessed the data and built our training and testing datasets, we can start to deploy different classification algorithms. It's relatively easy to test multiple models; as a result, we will compare and contrast the performance of ten different algorithms._____no_output_____

# Now that we have our dataset, we can start building algorithms! We'll need to import each algorithm we plan on using
# from sklearn.  We also need to import some performance metrics, such as accuracy_score and classification_report.

from sklearn.neighbors import KNeighborsClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.gaussian_process import GaussianProcessClassifier
from sklearn.gaussian_process.kernels import RBF
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.svm import SVC
from sklearn.metrics import classification_report, accuracy_score

# define scoring method
scoring = 'accuracy'

# Define models to train
names = ["Nearest Neighbors", "Gaussian Process",
         "Decision Tree", "Random Forest", "Neural Net", "AdaBoost",
         "Naive Bayes", "SVM Linear", "SVM RBF", "SVM Sigmoid"]

classifiers = [
    KNeighborsClassifier(n_neighbors = 3),
    GaussianProcessClassifier(1.0 * RBF(1.0)),
    DecisionTreeClassifier(max_depth=5),
    RandomForestClassifier(max_depth=5, n_estimators=10, max_features=1),
    MLPClassifier(alpha=1),
    AdaBoostClassifier(),
    GaussianNB(),
    SVC(kernel = 'linear'),
    SVC(kernel = 'rbf'),
    SVC(kernel = 'sigmoid')
]

models = zip(names, classifiers)

# evaluate each model in turn
results = []
names = []

for name, model in models:
    kfold = model_selection.KFold(n_splits=10, random_state = seed)
    cv_results = model_selection.cross_val_score(model, X_train, y_train, cv=kfold, scoring=scoring)
    results.append(cv_results)
    names.append(name)
    msg = "%s: %f (%f)" % (name, cv_results.mean(), cv_results.std())
    print(msg)Nearest Neighbors: 0.823214 (0.113908)
Gaussian Process: 0.873214 (0.056158)
Decision Tree: 0.750000 (0.185405)
Random Forest: 0.580357 (0.106021)
# Remember, performance on the training data is not that important. We want to know how well our algorithms
# can generalize to new data.  To test this, let's make predictions on the validation dataset.

for name, model in models:
    model.fit(X_train, y_train)
    predictions = model.predict(X_test)
    print(name)
    print(accuracy_score(y_test, predictions))
    print(classification_report(y_test, predictions))

# Accuracy - ratio of correctly predicted observation to the total observations.
# Precision - (false positives) ratio of correctly predicted positive observations to the total predicted positive observations
# Recall (Sensitivity) - (false negatives) ratio of correctly predicted positive observations to the all observations in actual class - yes.
# F1 score - F1 Score is the weighted average of Precision and Recall. Therefore, this score takes both false positives and false Nearest Neighbors
0.7777777777777778
             precision    recall  f1-score   support

          0       1.00      0.65      0.79        17
          1       0.62      1.00      0.77        10

avg / total       0.86      0.78      0.78        27

Gaussian Process
0.8888888888888888
             precision    recall  f1-score   support

          0       1.00      0.82      0.90        17
          1       0.77      1.00      0.87        10

avg / total       0.91      0.89      0.89        27

Decision Tree
0.7777777777777778
             precision    recall  f1-score   support

          0       1.00      0.65      0.79        17
          1       0.62      1.00      0.77        10

avg / total       0.86      0.78      0.78        27

Random Forest
0.5925925925925926
             precision    recall  f1-score   support

          0       0.88      0.41      0.56        17
          1       0.47      0.90      0.62        10

avg / total       0.73      0.59      0.58        27

Neural Net
0.9259259259259259
             precision    recall  f1-score   support

          0       1.00      0.88      0.94        17
          1       0.83      1.00      0.91        10

avg / total       0.94      0.93      0.93        27

AdaBoost
0.8518518518518519
             precision    recall  f1-score   support

          0       1.00      0.76      0.87        17
          1       0.71      1.00      0.83        10

avg / total       0.89      0.85      0.85        27

Naive Bayes
0.9259259259259259
             precision    recall  f1-score   support

          0       1.00      0.88      0.94        17
          1       0.83      1.00      0.91        10

avg / total       0.94      0.93      0.93        27

SVM Linear
0.9629629629629629
             precision    recall  f1-score   support

          0       1.00      0.94      0.97        17
          1       0.91      1.00      0.95        10

avg / total       0.97      0.96      0.96        27

SVM RBF
0.7777777777777778
             precision    recall  f1-score   support

          0       1.00      0.65      0.79        17
          1       0.62      1.00      0.77        10

avg / total       0.86      0.78      0.78        27

SVM Sigmoid
0.4444444444444444
             precision    recall  f1-score   support

          0       1.00      0.12      0.21        17
          1       0.40      1.00      0.57        10

avg / total       0.78      0.44      0.34        27


# Notebook from nnarenraju/ml-mock-data-challenge-1
Path: tutorials/Gravitational Waves/tutorial_gw.ipynb

# Tutorial: Gravitational Waves

## Waveform theory in short
Large portions of this section are based on [[1]](#gravitational_waves_v1) and chapter 2 of [[2]](#schaefer_master).

Gravitational waves are a prediction by Einstein's theory of general relativity. To find their existence one linearizes the Einstein equations

$$
R_{\mu\nu} - \frac{1}{2}R g_{\mu\nu} = \frac{16\pi G}{c^4} T_{\mu\nu}.
$$

To do so we assume that the spacetime is basically flat, i.e. Newtonian, with small pertubations. Mathematically this assumption can be expressed by

$$
g_{\mu\nu} = \eta_{\mu\nu} + h_{\mu\nu},
$$

where $\eta_{\mu\nu}$ is the flat, Minkowski metric and $h_{\mu\nu}$ are small deviations. Inserting this approximation into the Einstein equations and discarding all terms of order $\mathcal{O}\left(h^2\right)$ and above, together with the constraint of being in a vacuum, i.e. $T_{\mu\nu}=0$, yields

$$
\Box \bar{h}_{\mu\nu}=0,\ \bar{h}_{\mu\nu} = h_{\mu\nu}-\frac{1}{2}\eta_{\mu\nu}h.
$$

This is the familiar wave-equation that is known for instance from electro-magnetism. Therefore, we know that there are wave-like solutions to the Einstein equation. However, it only tells us gravitational waves can propagate through vacuum, but not how they look like.

To solve that question we need to consider the sources that produce these wave. This requires to drop the constraint $T_{\mu\nu}=0$. After a lot of mathematical tricks and using a specific frame of reference one arrives at the Quadrupole formula

$$
h_{ab}\left(t, \vec{x}\right) = \frac{2G}{c^4}\frac{1}{r}\frac{d^2}{dt^2}Q_{ab}\left(t-r/c\right).
$$

Here $Q_{ab}=I_{ab}-1/3\delta_{ab}I^c_c$ is the trace-corrected second mass moment and $I_{ab}$ is the original second mass moment, also known as the inertia tensor. Note that the indices are latin letters and are only meant to be evaluated on the 3 spatial dimensions.

This equation in principle allows us to calculate the waveform from a system whos dynamics are not influenced by the gravitational field or its radiation. However, we would like to calculate the motion of a binary system from which energy is carried away by gravitational waves. The energy radiatiated from a system by gravitational waves at any given time is known as the luminosity. Deriving it involves second order corrections in $h$ of the curvature tensor $R_{\mu\nu}$ and defining an effective energy-momentum tensor from these results. Doing so would go beyond the scope of this tutorial but you can check chapter 1 of [[1]](#gravitational_waves_v1) for more information. The result is

$$
L_{\text{GW}} = \frac{G}{5c^5}\langle\left(\frac{d^3}{dt^3}Q^{ab}\right)\left(\frac{d^3}{dt^3}Q_{ab}\right)\rangle,
$$

where $\langle\cdot\rangle$ is an average in time.

To derive an approximated form of the waveform goverened by the energy loss due to gravitational radiation one equates the luminosity with the negative time derivative of the orbital Newtonian energy

$$
L_{\text{GW}} \overset{!}{=} -\frac{d}{dt}E_\text{orbit} = -\frac{1}{2}\frac{G m_1 m_2 \frac{d}{dt}R}{R^2}.
$$

where $R$ is the orbital separation. For the next step a quasi-circular orbit of two point-masses is assumed. This in turn allows us to calculate $Q_{ab}$ and in turn $L_\text{GW}$. From this one obtains the two polarizations of the waveform

$$
\Phi\left(\tau\right) = -2 \left(\frac{5 G M_c}{c^3}\right)^{-5/8}\tau^{5/8}+\Phi_0\\
h_+\left(\tau\right) = \frac{1}{r}{\left(\frac{G M_c}{c^2}\right)}^{5/4}{\left(\frac{5}{c\tau}\right)}^{1/4}\left(\frac{1+\cos^2\left(\iota\right)}{2}\right)\cos\left(\Phi\left(\tau\right)\right)\\
h_\times\left(\tau\right) = \frac{1}{r}{\left(\frac{G M_c}{c^2}\right)}^{5/4}{\left(\frac{5}{c\tau}\right)}^{1/4}\cos\left(\iota\right)\sin\left(\Phi\left(\tau\right)\right)
$$

These waveforms have a lot of parameters. They are as follows:
 - $M_c = \frac{{\left(m_1 m_2\right)}^{3/5}}{{\left(m_1 + m_2\right)}^{1/5}}$ is known as the chirp-mass. It is the only way the component masses $m_1$ and $m_2$ influence the waveform.
 - $\tau$ is the time after merger. This means that the merger takes place at $\tau = 0$ and times prior to the merger are negative.
 - $\Phi_0$ is the phase of the binary system at merger, also known as coalescence time.
 - $r$ is the distance between the observer and the binary system.
 - $\iota$ is the inclination, i.e. the angle between the normal of the orbital plane and the line of sight of the observer.
 - $c$ is the speed of light in vacuum.
 - $G$ is the gravitational constant.

Below you can find a plot of the waveforms and you can play around with the different parameters._____no_output_____

%matplotlib widget
#%matplotlib tk
import matplotlib.pyplot as plt
plt.rcParams.update({'text.usetex': True})
from matplotlib.widgets import Slider
import numpy as np

#Define constants
c = 299792458
G = 6.67408 * 1e-11
msol = 1.98847 * 1e30
pc = 3.0857 * 1e16
t = np.linspace(-10, -0.01, 10000)

#Define physics functions
def mchirp(m1, m2):
    return (m1 * m2) ** (3. / 5.) / (m1 + m2) ** (1. / 5.)

def get_wave(m1, m2, coa_phase, iota, r):
    m1 *= msol
    m2 *= msol
    r *= 1e6 * pc
    phi = -2 * (5 * G * mchirp(m1, m2) / c ** 3) ** (-5. / 8.) * np.sign(t) * np.abs(t) ** (5. / 8.) + coa_phase
    hp = (G * mchirp(m1, m2) / c ** 2) ** (5. / 4.) * np.sign(t) * (5 / (c * np.abs(t))) ** (1. / 4.) * ((1 + np.cos(iota) ** 2) / 2) * np.cos(phi) / r
    hc = (G * mchirp(m1, m2) / c ** 2) ** (5. / 4.) * np.sign(t) * (5 / (c * np.abs(t))) ** (1. / 4.) * np.cos(iota) * np.sin(phi) / r
    return hp, hc

#Set initial parameters
init_m1 = 15
m1_lim = (1, 30)
init_m2 = 12
m2_lim = (1, 30)
init_coa_phase = 0.1
coa_phase_lim = (0., 2 * np.pi)
init_iota = 0.
iota_lim = (0., 2 * np.pi)
init_r = 1500
r_lim = (1000, 2000)

#Set up plot
fig, axs = plt.subplots(nrows=7, gridspec_kw={'height_ratios': [2] * 2 + [0.2] * 5}, figsize=(12.8, 7.2), num='Linearized Gravity Waveform Polarizations')
hp, hc = get_wave(init_m1, init_m2, init_coa_phase, init_iota, init_r)
hpline, = axs[0].plot(t, hp, color='black')
hcline, = axs[1].plot(t, hc, color='black')
axs[0].set_xlabel('Time [s]')
axs[0].set_ylabel('Strain')
axs[1].set_xlabel('Time [s]')
axs[1].set_ylabel('Strain')

hpex, hcex = get_wave(m1_lim[1], m2_lim[1], coa_phase_lim[1], iota_lim[0], r_lim[0]) #Calculate wave at extremal values
ylim = 1.1 * max(np.abs(hpex).max(), np.abs(hcex).max())
axs[0].set_ylim(-ylim, ylim)
axs[1].set_ylim(-ylim, ylim)

m1_slider = Slider(ax=axs[2],
                   label='Primary mass (m1)',
                   valmin=m1_lim[0],
                   valmax=m1_lim[1],
                   valinit=init_m1)
m2_slider = Slider(ax=axs[3],
                   label='Secondary mass (m2)',
                   valmin=m2_lim[0],
                   valmax=m2_lim[1],
                   valinit=init_m2)
coa_phase_slider = Slider(ax=axs[4],
                          label='Coalescence phase',
                          valmin=coa_phase_lim[0],
                          valmax=coa_phase_lim[1],
                          valinit=init_coa_phase)
iota_slider = Slider(ax=axs[5],
                     label='Inclination',
                     valmin=iota_lim[0],
                     valmax=iota_lim[1],
                     valinit=init_iota)
r_slider = Slider(ax=axs[6],
                  label='Distance',
                  valmin=r_lim[0],
                  valmax=r_lim[1],
                  valinit=init_r)

def update(val):
    hp, hc = get_wave(m1_slider.val,
                      m2_slider.val,
                      coa_phase_slider.val,
                      iota_slider.val,
                      r_slider.val)
    hpline.set_ydata(hp)
    hcline.set_ydata(hc)

m1_slider.on_changed(update)
m2_slider.on_changed(update)
coa_phase_slider.on_changed(update)
iota_slider.on_changed(update)
r_slider.on_changed(update)
fig.tight_layout()
None_____no_output_____

These waveforms are calculated in the reference-frame of the source. They need to be projected onto a detector in order to simulate a measurable output. The calculations for a Michelson-interferometer with an angle of 90° between the arms (the kind of instrument used by LIGO, Virgo, and Kagra) are somewhat lengthy and make use of Euler-angles. But at the end one arrives at the detector readout

$$
\delta l = F_+\left(\theta, \varphi\right)\left(\cos\left(2\psi\right)h_+-\sin\left(2\psi\right)h_\times\right)+F_\times\left(\theta, \varphi\right)\left(\sin\left(2\psi\right)h_++\cos\left(2\psi\right)h_\times\right),
$$
with
$$
F_+\left(\theta, \varphi\right) = \frac{1}{2}\left(1+\cos^2\left(\theta\right)\right)\cos\left(2\varphi\right),\\
F_\times\left(\theta, \varphi\right) = \cos\left(\theta\right)\sin\left(2\varphi\right).
$$

Here $\varphi$ is the right ascension and $\theta$ the declination with the detector as a reference plane. The angle $\psi$ is the polarization angle and it is the angle by which the plane of orbital motion has to be rotated around the line of sight of the observer.

Below you can find a plot of the projected waveforms and can play around with the sky-position (i.e. $\theta$ and $\varphi$) as well as the polarization angle._____no_output_____

#Initial parameters
init_theta = 0
theta_min, theta_max = (0, np.pi)
init_phi = 0
phi_min, phi_max = (0, 2 * np.pi)
init_psi = 0
psi_min, psi_max = (0, 2 * np.pi)

#Function definitions
def fplus(theta, phi):
    return (1 + np.cos(theta) ** 2) * np.cos(2 * phi) / 2

def fcross(theta, phi):
    return np.cos(theta) * np.sin(2 * phi)

def project_wave(hp, hc, theta, phi, psi):
    fp = fplus(theta, phi)
    fc = fcross(theta, phi)
    return fp * (np.cos(2 * psi) * hp - np.sin(2 * psi) * hc) + fc * (np.sin(2 * psi) * hp + np.cos(2 * psi) * hc)

#Plotting
hp, hc = get_wave(init_m1, init_m2, init_coa_phase, init_iota, init_r)
wav = project_wave(hp, hc, init_theta, init_phi, init_psi)

fig, axs = plt.subplots(nrows=4, gridspec_kw={'height_ratios': [2] * 1 + [0.2] * 3}, figsize=(12.8, 7.2), num='Linearized Gravity Projected Wave')
line, = axs[0].plot(t, wav, color='black')
axs[0].set_xlabel('Time [s]')
axs[0].set_ylabel('Strain')

theta_slider = Slider(ax=axs[1],
                      label='Declination',
                      valmin=theta_min,
                      valmax=theta_max,
                      valinit=init_theta)
phi_slider = Slider(ax=axs[2],
                    label='Right ascension',
                    valmin=phi_min,
                    valmax=phi_max,
                    valinit=init_phi)
psi_slider = Slider(ax=axs[3],
                    label='Polarization angle',
                    valmin=psi_min,
                    valmax=psi_max,
                    valinit=init_psi)

def update(val):
    wav = project_wave(hp,
                       hc,
                       theta_slider.val,
                       phi_slider.val,
                       psi_slider.val)
    line.set_ydata(wav)

theta_slider.on_changed(update)
phi_slider.on_changed(update)
psi_slider.on_changed(update)
fig.tight_layout()_____no_output_____

This waveform is a nice approximation but it has several problems. Most importantly the approximation assumes that the orbital dynamics are given by Newtonian mechanics. Therefore, no general relativistic effects other than the gravitational radiation are considered. Mathematically this is expressed in the approximation $g_{\mu\nu}\approx\eta_{\mu\nu}+h_{\mu\nu}$, with $h$ small. This means that the geometry of the spacetime is basically given by the flat, special relativitic metric $\eta_{\mu\nu}$. However, as the two bodies orbit each other they loose orbital energy. In turn the bodies have to come closer together, thus spiraling towards each other. At some point the two objects will be too close for the assumption of an almost flat geometry to hold true. Thus the approximation breaks down the closer we get to the merger of the two objects.

Secondly, we have not considered any effects caused by the spins of the two objects. Considering them leads to effects like precession.

Finally, the waveform depends on the component masses only through the combination known as the chirp-mass $M_c$. This means that if the waveforms would be accurate, we would have no means of determining the individual masses of the two bodies.

For these reasons one can use a different approximation, where the full metric $g_{\mu\nu}$ is expanded in powers of $v/c$, where $v$ is the velocity of the orbiting objects. This approximation is known as the post-Newtonian formalism. We will not go into details here (check chapter 5 of [[1]](#gravitational_waves_v1) for more information), but simply mention that to first order we get back the familiar linearized equations. For higher orders we can determine spin-effects and also have a dependence on the mass-ratio $q$, which allows us to disentangle the degeneracy of the masses. However, since this is a higher-order effect, its influence on the waveform is much smaller. Therefore, when parameters of astrophysical signals are given, the chirp-mass is usually one of the best constrained parameters, whereas the error on the mass-ratio and in extension the errors for the individual component masses are much larger.

Still, even the post-Newtonian waveforms are not valid at or very close to the merger. The most accurate waveform models we have come from numerical relativity. These numerically solve the full Einstein equations, i.e. determine the components of the metric from the mass-distribution specified by the energy-momentum tensor. However, since they are highly non-linear these calculations can take weeks on the largest super computers. This makes them infeasible for many purposes, such as fast data simulation and most current data analysis methods (see below).

For this reason the community has developed a plethora of waveform-approximants. The main approximation methods are Phenom-models (join analytical models of the inspiral with analytical models of the ringdown by tuning parameters to numerical relativity), EOB-models (tune parameters of a Hamiltonian to numerical relativity), and NR-surrogate models (interpolation of numerical relativity waveforms). In the MLGWSC-1 we use a waveform-approximant known as `IMRPhenomXPHM` [[3]](#imrphenomxphm). This seems like a very chaotic name, but it has a lot of structure that tells you the main features of the waveform. `IMR` stands for inspiral-merger-ringdown, i.e. the wavefrom models all stages of the coalescence of a binary system. `Phenom` specifies the kind of waveform approximation-method used. `X` is a newer version of the `Phenom` models. The letter `P` signifies that the model is able to deal with non-aligned spins and calculate precession effects. Finally, the letters `HM` signify that the waveform model is also able to calculate higher harmonics. The waveform can be decomposed into spherical harmonics. Usually only the fundamental $22$-mode is considered, as it is strongly dominant. However, for systems where the mass-ratio is very unequal higher harmonics can lead to non-negligable contributions [[4]](#GW190814) to the detectable signal. `IMRPhenomXPHM` is one of the most accurate waveform models that are currently available, which is why we chose it for our challenge._____no_output_____## Effects of parameters on the signal
To qualitatively understand the morpholgy of signals from different binary systems, we will summarize the effect the change of different parameters has on the waveform. For this we will consider the total mass $M=m_1 + m_2$ and the chirp-mass $M_c=\frac{{\left(m_1 m_2\right)}^{3/5}}{{\left(m_1 + m_2\right)}^{1/5}}$ instead of the individual component masses $m_1$ and $m_2$.

|                      | Parameter name      | Common symbol                                       | Effect on waveform                                                                                |
|----------------------|---------------------|-----------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Intrinsic parameters | Chirp mass          | $M_c$                                               | To first order the chirp mass is the parameter that influences the frequency evolution of the signal. This can be seen by considering the phase of the gravitational wave $\Phi\left(\tau\right)$. The only parameter other than constants that directly interfaces with the time dependence is the chirp-mass. It also scales the amplitude of the signals. However, this effect is not as important, as it is not degenerate with the distance or inclination, due to the effect on the frequency evolution.                          |
|                      | Total mass          | $M$                                                 | The primary effect of the total mass is its influence on the cutoff-frequency, i.e. the gravitational-wave frequency at which the two bodies merge. The larger the total mass, the lower the maximum frequency. This can be understood from the fact that heavier (compact) objects are larger than lighter ones. Therefore, their minimum orbital separation is larger which in turn results in a slower orbit. As a secondary effect, the total mass also scales the amplitude of the signal. The larger the total mass, the higher the amplitude. |
|                      | Mass ratio          | $q$                                                 | The mass ratio is important for effects such as higher order multipoles and precession. A larger mass ratio (i.e. a less equal mass distribution between the constituents of the system) results in a larger effect of the higher order modes and precession.                                                                                |
|                      | Spins               | $\vec{\chi}_1, \vec{\chi}_2$ | The spins can have varying effects and they depend mostly on their magnitude and their alignment with the orbital angular momentum. When they are aligned with the orbital angular momentum they mostly have an effect on the time-scale of the inspiraling system. The more aligned they are the longer the system takes to coalesce. In cases where they are not aligned with the orbital angular momentum they introduce precession effects on the system.                                                                                |
| Extrinsic parameters | Coalescence phase   | $\Phi_0$                                            | The coalescence phase specifies the phase of the signal at merger. Therefore, it shifts the phase of the signal by a constant amount.                                                                                |
|                      | Coalescence time    | $t_0$                                               | The time that specifies the merger. Altering this parameter shifts the waveform in the time domain by a constant amount.                                                                                |
|                      | Inclination         | $\iota$                                             | The inclination specifies the angle between the angle between the orbital angular momentum and the line of sight of the observer. Without higher multipoles it is degenerate with the distance, as both influence only the amplitude of the signal. If higher multipoles can be observed, this degeneracy is broken. The same is true for precession effects.                                                                                |
|                      | Luminosity distance | $r$                                         | The distance between the observer and the radiating system. It scales the amplitude of the signal by a factor $1/r$. Without higher harmonics or precession it is degenerate with the effects of the inclination.                                                                                |
|                      | Right ascension     | $\alpha$                               | The right ascension of the system. In the challenge this right ascension is given with respect to the standard geocentric coordinates. For a single detector it is degenerate with the distance. With multiple detectors the sky position can be constrained by the difference in the arrival times of the signal in the different detectors.                                                                                |
|                      | Declination         | $\delta$                                | The declination of the system. In the challenge this declination is given with respect to the standard geocentric coordinates. For a single detector it is degenerate with the distance. With multiple detectors the sky position can be constrained by the difference in the arrival times of the signal in the different detectors.                                                                                |
|                      | Polarization angle  | $\psi$                                              | The polarization angle specifies the angle by which the plane of the orbital motion has to be rotated around the line of sight of the observer. It has a relation with the polarization of the wave.                                                                                |

Below you can play around with the individual waveform parameters. Not all combinations of $M_c$ and $M$ are allowed. If an impossible combination is chose, the graph will simply display zeros. Generally, the slider for the chirp mass must be to the left or on equal "height" as the total mass slider._____no_output_____

from matplotlib.gridspec import GridSpec
from pycbc.detector import Detector
from pycbc.waveform import get_td_waveform
from pycbc.coordinates import spherical_to_cartesian

#Constants
det = Detector('H1')

#Function definitions
def mass1_mass2_from_mchirp_mtotal(mchirp, mtotal):
    m1 = mtotal / 2 + ((mtotal / 2) ** 2 - (mchirp * mtotal ** (1. / 5.)) ** (5. / 3.)) ** (1. / 2.)
    m2 = mtotal - m1
    return m1, m2

def get_wav(m1, m2, spin1x, spin1y, spin1z, spin2x, spin2y, spin2z, inclination, distance, coa_phase, tc, ra, dec, pol):
    hp, hc = get_td_waveform(approximant='IMRPhenomXPHM',
                             f_lower=20,
                             delta_t=1. / 2048,
                             mass1=m1,
                             mass2=m2,
                             inclination=inclination,
                             distance=distance,
                             spin1x=spin1x,
                             spin1y=spin1y,
                             spin1z=spin1z,
                             spin2x=spin2x,
                             spin2y=spin2y,
                             spin2z=spin2z,
                             coa_phase=coa_phase
                            )
    hp.start_time = float(hp.start_time) + tc
    hc.start_time = float(hc.start_time) + tc
    wav = det.project_wave(hp, hc, ra, dec, pol)
    wav = wav.time_slice(float(wav.end_time) - 3, float(wav.end_time))
    return wav

#Plotting
fig = plt.figure(figsize=(12.8, 7.2), num='Parameter influence')
gs = GridSpec(4, 5, figure=fig, height_ratios=[1] + [0.1] * 3)
axs = []
axs.append(fig.add_subplot(gs[0, :])) #Main plot
for j in range(5):
    for i in range(1, 4):
        axs.append(fig.add_subplot(gs[i, j]))
#fig, axs = plt.subplots(nrows=16, gridspec_kw={'height_ratios': [2] * 1 + [0.2] * 15}, figsize=(12.8, 6*7.2), num='Parameter influence')

init_mchirp = 32.5
min_mchirp, max_mchirp = 26, 43.5
init_mtotal = 75
min_mtotal, max_mtotal = 60, 100
init_spin1a = 0
min_spin1a, max_spin1a = 0, 0.99
init_spin1az = 0
min_spin1az, max_spin1az = 0, 2 * np.pi
init_spin1po = 0
min_spin1po, max_spin1po = 0, np.pi
init_spin2a = 0
min_spin2a, max_spin2a = 0, 0.99
init_spin2az = 0
min_spin2az, max_spin2az = 0, 2 * np.pi
init_spin2po = 0
min_spin2po, max_spin2po = 0, np.pi
init_inclination = 0
min_inclination, max_inclination = 0, 2 * np.pi
init_distance = 1000
min_distance, max_distance = 500, 1500
init_coa_phase = 0
min_coa_phase, max_coa_phase = 0, 2 * np.pi
init_tc = 0
min_tc, max_tc = -1, 1
init_ra = 0
min_ra, max_ra = 0, 2 * np.pi
init_dec = 0
min_dec, max_dec = 0, np.pi
init_pol = 0
min_pol, max_pol = 0, 2 * np.pi

init_mass1, init_mass2 = mass1_mass2_from_mchirp_mtotal(init_mchirp, init_mtotal)
init_spin1x, init_spin1y, init_spin1z = spherical_to_cartesian(init_spin1a, init_spin1az, init_spin1po)
init_spin2x, init_spin2y, init_spin2z = spherical_to_cartesian(init_spin2a, init_spin2az, init_spin2po)

wav = get_wav(init_mass1,
              init_mass2,
              init_spin1x,
              init_spin1y,
              init_spin1z,
              init_spin2x,
              init_spin2y,
              init_spin2z,
              init_inclination,
              init_distance,
              init_coa_phase,
              init_tc,
              init_ra,
              init_dec,
              init_pol)

line, = axs[0].plot(wav.sample_times, wav, color='black')
axs[0].set_xlabel('Time [s]')
axs[0].set_ylabel('Strain')

i = 1
mchirp_slider = Slider(ax=axs[i],
                       label='Chirp-Mass',
                       valmin=min_mchirp,
                       valmax=max_mchirp,
                       valinit=init_mchirp)
i += 1
mtotal_slider = Slider(ax=axs[i],
                       label='Total-Mass',
                       valmin=min_mtotal,
                       valmax=max_mtotal,
                       valinit=init_mtotal)
i += 1
distance_slider = Slider(ax=axs[i],
                         label='Luminosity distance',
                         valmin=min_distance,
                         valmax=max_distance,
                         valinit=init_distance)
i += 1
spin1a_slider = Slider(ax=axs[i],
                       label='Spin 1 Amplitude',
                       valmin=min_spin1a,
                       valmax=max_spin1a,
                       valinit=init_spin1a)
i += 1
spin1az_slider = Slider(ax=axs[i],
                       label='Spin 1 Azimutal',
                       valmin=min_spin1az,
                       valmax=max_spin1az,
                       valinit=init_spin1az)
i += 1
spin1po_slider = Slider(ax=axs[i],
                        label='Spin 1 Polar',
                        valmin=min_spin1po,
                        valmax=max_spin1po,
                        valinit=init_spin1po)
i += 1
spin2a_slider = Slider(ax=axs[i],
                       label='Spin 2 Amplitude',
                       valmin=min_spin2a,
                       valmax=max_spin2a,
                       valinit=init_spin2a)
i += 1
spin2az_slider = Slider(ax=axs[i],
                        label='Spin 2 Azimutal',
                        valmin=min_spin2az,
                        valmax=max_spin2az,
                        valinit=init_spin2az)
i += 1
spin2po_slider = Slider(ax=axs[i],
                        label='Spin 2 Polar',
                        valmin=min_spin2po,
                        valmax=max_spin2po,
                        valinit=init_spin2po)
i += 1
inclination_slider = Slider(ax=axs[i],
                            label='Inclination',
                            valmin=min_inclination,
                            valmax=max_inclination,
                            valinit=init_inclination)
i += 1
coa_phase_slider = Slider(ax=axs[i],
                          label='Coalescence Phase',
                          valmin=min_coa_phase,
                          valmax=max_coa_phase,
                          valinit=init_coa_phase)
i += 1
tc_slider = Slider(ax=axs[i],
                   label='Coalescence Time',
                   valmin=min_tc,
                   valmax=max_tc,
                   valinit=init_tc)
i += 1
ra_slider = Slider(ax=axs[i],
                   label='Right Ascension',
                   valmin=min_ra,
                   valmax=max_ra,
                   valinit=init_ra)
i += 1
dec_slider = Slider(ax=axs[i],
                    label='Declination',
                    valmin=min_dec,
                    valmax=max_dec,
                    valinit=init_dec)
i += 1
pol_slider = Slider(ax=axs[i],
                    label='Polarization Angle',
                    valmin=min_pol,
                    valmax=max_pol,
                    valinit=init_pol)

def update(val):
    mass1, mass2 = mass1_mass2_from_mchirp_mtotal(mchirp_slider.val,
                                                  mtotal_slider.val)
    if isinstance(mass1, complex) or isinstance(mass2, complex) or not np.isfinite(mass1) or not np.isfinite(mass2):
        line.set_ydata(np.zeros(len(line.get_xdata())))
        return
    spin1x, spin1y, spin1z = spherical_to_cartesian(spin1a_slider.val,
                                                    spin1az_slider.val,
                                                    spin1po_slider.val)
    spin2x, spin2y, spin2z = spherical_to_cartesian(spin2a_slider.val,
                                                    spin2az_slider.val,
                                                    spin2po_slider.val)
    wav = get_wav(mass1,
                  mass2,
                  spin1x,
                  spin1y,
                  spin1z,
                  spin2x,
                  spin2y,
                  spin2z,
                  inclination_slider.val,
                  distance_slider.val,
                  coa_phase_slider.val,
                  tc_slider.val,
                  ra_slider.val,
                  dec_slider.val,
                  pol_slider.val
                 )
    line.set_xdata(wav.sample_times)
    line.set_ydata(wav.numpy())

mchirp_slider.on_changed(update)
mtotal_slider.on_changed(update)
spin1a_slider.on_changed(update)
spin1az_slider.on_changed(update)
spin1po_slider.on_changed(update)
spin2a_slider.on_changed(update)
spin2az_slider.on_changed(update)
spin2po_slider.on_changed(update)
inclination_slider.on_changed(update)
distance_slider.on_changed(update)
coa_phase_slider.on_changed(update)
tc_slider.on_changed(update)
ra_slider.on_changed(update)
dec_slider.on_changed(update)
pol_slider.on_changed(update)
fig.tight_layout()_____no_output_____

## Detector noise

We know what we are looking for, but why is it still so difficult to detect gravitational waves? It has taken us more than 100 years to find the first signal.

The short answer to this question is: The length changes are very small ($10^{-21}$ meters) and on those length scales the detectors are very noisy. To even be able to detect the length deviation of less than a proton on a distance of 4km some new technology had to be developed.

Below we will briefly go over the conceptual detector design and discuss a few noise sources. Afterwards we will talk about the characteristics of the noise and how it is processed to reduce its impact on the data-analysis.

### The detector and its noise sources
When a gravitational wave passes through a plane, it stretches it in one direction and compresses it in the orthogonal direction. This is a periodic process that acts as a force on rigid bodies. For light this compression and stretching manifests itself as a change in the optical path length between two points in the plane.

Current detectors are high-quality Michelson-interferometers with an arm-length of 4km. Below you can find a qualitative sketch of the setup. It works by splitting a laserbeam into two parts, each of which travel a given distance before being reflected and re-combined. When these beams are re-combined they form an interference pattern, which depends on the difference in the optical path length between their two trajectories. Since a gravitational wave changes the optical path length differently in orthogonal directions and because it is periodic, one can in principle detect it by observing the change in the interference pattern at the output (in the image below the output is at the bottom).

![Michelson Interferometer](Basic_michelson_labeled.jpg)

[Source: https://www.ligo.caltech.edu/page/ligos-ifo](https://www.ligo.caltech.edu/page/ligos-ifo)

However, gravitational waves are not the only things that may change the optical path length in the arms. Other such processes are known as noise sources. Some examples include (a non-exhaustive list)
- Seismic noise: Earth quakes, ground vibrations, local shift of the gravitational potential. This noise source is dominant at low frequencies < ~15 Hz.
- Quantum shot noise: Fluctuations at the output port caused by vacuum fluctuations. This noise source is one of the limiting factors in the most sensitive regions of the detector around ~100 Hz and dominant at high frequencies
- Thermal coating noise: The vibrations of the optical coating on the mirrors due to temperature fluctuations. One of the largest noise sources overall.
- ...

Extensive effort has been done to reduce the impact of any of these sources and new technologies were developed. If you want to learn more about the detectors and their noise please refer to [[5]](#aligo).

### Noise characteristics
The most important tool to characterize the detector noise is to determine the power of the noise in the output at different frequencies. The resulting function is known as the power spectral density (PSD) and often given by the symbol $S_n(f)$. To estimate it in principle one just has to calculate the power of the noise in the fourier domain averaged over all times

$$
S_n(f) = \langle n^\ast(f) n(f)\rangle.
$$

In practice, however, there is only a finite amount of data available and the detector state changes over time, resulting in different power spectrums at different times. Therefore, usually the Welch's method is applied to estimate the PSD at different times.

Welch's method splits a long stretch of data into multiple smaller chunks. These chunks have a 50% overlap, i.e. the boundaries of the next chunk are the boundaries of the previous chunk shifted by half the chunk-size. Then a windowing function (usually a Hanning window) is applied to each chunk and a PSD is calculated for all of them. They are averaged to produce a robust estimate of the total PSD of the long input data. This allows to have a rolling buffer of PSD estimates that changes over time when even longer stretches of data are analyzed or streaming data is considered.

Calculating the PSD by Welch's method and taking the square root of the resulting PSD yields an estimate of the kind that is shown below. The squareroot of the PSD is called the amplitude spectral density (ASD). The plot also shows the squareroot of the power of a GW150914-like signal. One can see that for a certain frequency range the power of the signal exceeds the power of the noise. For this reason it can be detected._____no_output_____

from pycbc.waveform import get_fd_waveform
from pycbc.detector import Detector
from pycbc import DYN_RANGE_FAC
from pycbc.psd import welch
from pycbc.types import load_timeseries

#Calculate PSD
ts = load_timeseries('real_noise.hdf')
psd = welch(ts, 2048, 1024)
asd = psd ** 0.5

#Calculate signal
gps_time = 1126259462
hp, hc = get_fd_waveform(approximant='IMRPhenomXPHM',
                         delta_f=1./64,
                         f_lower=10,
                         mass1=35.3,
                         mass2=29.6,
                         distance=440)
hp.start_time = float(hp.start_time) + gps_time
hc.start_time = float(hc.start_time) + gps_time
det = Detector('H1')
ra, dec = det.optimal_orientation(gps_time)
fp, fc = det.antenna_pattern(ra, dec, 0, gps_time)
wav = fp * hp + fc * hc
wav_asd = (wav * wav.conj()) ** 0.5

#Restrict to certain frequency range
fmin, fmax = 10, 300
wavsf = wav_asd.sample_frequencies
wavidxs = np.where(np.logical_and(fmin < wavsf, wavsf < fmax))[0]

#Plot
fig, ax = plt.subplots(figsize=(12.8, 7.2), num='Noise curve')
ax.semilogy(asd.sample_frequencies, asd, label='ASD')
ax.semilogy(wav_asd.sample_frequencies[wavidxs], wav_asd[wavidxs],
            label='Signal Power')
ax.legend()
ax.grid()
ax.set_xlabel('Frequency [Hz]')
ax.set_ylabel('Strain $1/\\sqrt{Hz}$')
None/home/marlin/environments/test3d7/lib/python3.7/site-packages/matplotlib/cbook/__init__.py:1333: ComplexWarning: Casting complex values to real discards the imaginary part
  return np.asarray(x, float)

Once this noise curve is known, frequencies at which the detector is particularly noisy can be downweight. This process is called whitening. To whiten the data, it is fourier transformed, divided by the ASD, and transformed back into the time domain. For technical reasons also some data in the beginning and end of each whitened segment is thrown away. If the PSD is used instead of the ASD the process is called over-whitening.

After whitening the data it has a PSD of roughly 1 for all frequencies. Signals will, therefore, be visible in the power spectrum as a deviation from unit power in a given frequency range. See the plot below._____no_output_____

asdf = wav_asd.sample_frequencies[wavidxs]
asdinterp = np.interp(asdf, asd.sample_frequencies, asd.numpy())
fig, ax = plt.subplots(figsize=(12.8, 7.2), num='Noise curve whitened')
ax.semilogy(asd.sample_frequencies, asd / asd, label='ASD')
ax.semilogy(asdf, wav_asd[wavidxs] / asdinterp,
            label='Signal Power')
ax.legend()
ax.grid()
ax.set_xlabel('Frequency [Hz]')
ax.set_ylabel('Strain $1/\\sqrt{Hz}$')
None/home/marlin/environments/test3d7/lib/python3.7/site-packages/matplotlib/cbook/__init__.py:1333: ComplexWarning: Casting complex values to real discards the imaginary part
  return np.asarray(x, float)

Additionally, because the detector is especially noisy at very low and very high frequencies, the data is often also bandbassed. A before and after plot for GW150914 can be seen below._____no_output_____

#Code adapted from: https://pycbc.org/pycbc/latest/html/gw150914.html
from pycbc.catalog import Merger
from pycbc.filter import lowpass_fir, highpass_fir

strain = Merger('GW150914').strain('H1')
strain = highpass_fir(strain, 15, 8)
white_strain = strain.whiten(4, 1, low_frequency_cutoff=15)
bandpassed_strain = lowpass_fir(highpass_fir(white_strain, 35, 8), 300, 8)

fig, axs = plt.subplots(ncols=2, figsize=(12.8, 7.2), num='Whitened strain')
axs[0].plot(strain.sample_times, strain)
axs[0].set_xlim(1126259462.21, 1126259462.45)
axs[0].set_xlabel('GPS-Time [s]')
axs[0].set_ylabel('Raw Strain')

axs[1].plot(bandpassed_strain.sample_times, bandpassed_strain)
axs[1].set_xlim(1126259462.21, 1126259462.45)
axs[1].set_xlabel('GPS-Time [s]')
axs[1].set_ylabel('Processed Strain')
fig.tight_layout()_____no_output_____

The above discussion assumes that the noise is basically Gaussian and that it is only non-stationary on large time-scales. However, in practice there are short duration noise-transients that contaminate the data. These are known as glitches and some can mimick the behavior of gravitational waves. They come in multiple different shapes and morphologies and only for some of them the cause is known. Some are due to scattered light, others are introduced by the electronics, but some just appear without a known cause. Furthermore, glitches are not rare events. They can occur at rates up to one every few minutes. The best way to visualize them are so called q-scans. A q-scan is a time-frequency analysis of the data. Below we show the q-scan of GW150914 (first plot) and a glitch (second plot)._____no_output_____

qt, qf, qv = bandpassed_strain.time_slice(1126259461.71, 1126259462.95).qtransform(.002, logfsteps=100, qrange=(10, 10), frange=(20, 512))
fig, ax = plt.subplots(figsize=(12.8, 7.2), num='Q-Transform GW150914')

im = ax.pcolormesh(qt, qf, qv, cmap='viridis', shading='auto')
cbar = fig.colorbar(im)_____no_output_____

![Example glitch](glitch.png)

Source: [https://www.zooniverse.org/projects/zooniverse/gravity-spy/about/research](https://www.zooniverse.org/projects/zooniverse/gravity-spy/about/research)_____no_output_____Once detected, these glitches are usually windowed out from the data. In turn some information in the data is lost, which can hurt the recoverability of signals that overlap with a glitch, as was the case for GW170817 [[6]](#GW170817).

With this we conclude this introductory tutorial to gravitational-wave data analysis._____no_output_____## References
- [1]: [Gravitational Waves Volume 1: Theory and Experiments](https://oxford.universitypressscholarship.com/view/10.1093/acprof:oso/9780198570745.001.0001/acprof-9780198570745), Michele Maggiore, 2007, Oxford University Press
- [2]: [Analysis of Gravitational-Wave Signals from Binary Neutron Star Mergers Using Machine Learning](https://doi.org/10.15488/7467), Marlin B. Schäfer, 2019, Master thesis
- [3]: [Computationally efficient models for the dominant and subdominant harmonic modes of precessing binary black holes](https://doi.org/10.1103/PhysRevD.103.104056), Geraint Pratten et al., 2020, Phys. Rev. D 103, 104056
- [4]: [GW190814: Gravitational Waves from the Coalescence of a 23 Solar Mass Black Hole with a 2.6 Solar Mass Compact Object](https://doi.org/10.3847/2041-8213/ab960f), R. Abbott et al, 2020, ApJL 896 L44
- [5]: [Advanced LIGO](https://doi.org/10.1088/0264-9381/32/7/074001), The LIGO Scientific Collaboration et al., 2015, Class. Quantum Grav. 32 074001
- [6]: [GW170817: Observation of Gravitational Waves from a Binary Neutron Star Inspiral](https://doi.org/10.1103/PhysRevLett.119.161101), B. P. Abbott et al. (LIGO Scientific Collaboration and Virgo Collaboration), 2017, Phys. Rev. Lett. 119, 161101_____no_output_____


# Notebook from fgrelard/SimpleITK-Notebooks
Path: Python/01_Image_Basics.ipynb

# Welcome to the first SimpleITK Notebook demo:
# SimpleITK Image Basics

This document will give a brief orientation to the SimpleITK Image class.

First we import the SimpleITK Python module. By convention our module is imported into the shorter and more Pythonic "sitk" local name.
_____no_output_____

from __future__ import print_function
%matplotlib inline
import matplotlib.pyplot as plt
import SimpleITK as sitk
_____no_output_____

## Image Construction

There are a variety of ways to create an image. All images' initial value is well defined as zero._____no_output_____

image = sitk.Image(256, 128, 64, sitk.sitkInt16)
image_2D = sitk.Image(64, 64, sitk.sitkFloat32)
image_2D = sitk.Image([32,32], sitk.sitkUInt32)
image_RGB = sitk.Image([128,128], sitk.sitkVectorUInt8, 3)_____no_output_____

### Pixel Types

The pixel type is represented as an enumerated type. The following is a table of the enumerated list.

sitkUInt8	Unsigned 8 bit integer

sitkInt8	Signed 8 bit integer

sitkUInt16	Unsigned 16 bit integer

sitkInt16	Signed 16 bit integer

sitkUInt32	Unsigned 32 bit integer

sitkInt32	Signed 32 bit integer

sitkUInt64	Unsigned 64 bit integer

sitkInt64	Signed 64 bit integer

sitkFloat32	32 bit float

sitkFloat64	64 bit float

sitkComplexFloat32	complex number of 32 bit float

sitkComplexFloat64	complex number of 64 bit float

sitkVectorUInt8	Multi-component of unsigned 8 bit integer

sitkVectorInt8	Multi-component of signed 8 bit integer

sitkVectorUInt16	Multi-component of unsigned 16 bit integer

sitkVectorInt16	Multi-component of signed 16 bit integer

sitkVectorUInt32	Multi-component of unsigned 32 bit integer

sitkVectorInt32	Multi-component of signed 32 bit integer

sitkVectorUInt64	Multi-component of unsigned 64 bit integer

sitkVectorInt64	Multi-component of signed 64 bit integer

sitkVectorFloat32	Multi-component of 32 bit float

sitkVectorFloat64	Multi-component of 64 bit float

sitkLabelUInt8	RLE label of unsigned 8 bit integers

sitkLabelUInt16	RLE label of unsigned 16 bit integers

sitkLabelUInt32	RLE label of unsigned 32 bit integers

sitkLabelUInt64	RLE label of unsigned 64 bit integers

There is also `sitkUnknown`, which is used for undefined or erroneous pixel ID's. It has a value of -1.

The 64-bit integer types are not available on all distributions. When not available the value is `sitkUnknown`.
_____no_output_____### More Information about the Image class be obtained in the Docstring

SimpleITK classes and functions have the Docstrings derived from the C++ definitions and the Doxygen documentation. _____no_output_____

help(image)_____no_output_____

## Accessing Attributes

If you are familiar with ITK, then these methods will follow your expectations:_____no_output_____

print(image.GetSize())
print(image.GetOrigin())
print(image.GetSpacing())
print(image.GetDirection())
print(image.GetNumberOfComponentsPerPixel())_____no_output_____

Note: The starting index of a SimpleITK Image is always 0. If the output of an ITK filter has non-zero starting index, then the index will be set to 0, and the origin adjusted accordingly.

The size of the image's dimensions have explicit accessors:_____no_output_____

print(image.GetWidth())
print(image.GetHeight())
print(image.GetDepth())_____no_output_____

Since the dimension  and pixel type of a SimpleITK image is determined at run-time accessors are needed. _____no_output_____

print(image.GetDimension())
print(image.GetPixelIDValue())
print(image.GetPixelIDTypeAsString())_____no_output_____

What is the depth of a 2D image?_____no_output_____

print(image_2D.GetSize())
print(image_2D.GetDepth())_____no_output_____

What is the dimension and size of a Vector image?_____no_output_____

print(image_RGB.GetDimension())
print(image_RGB.GetSize())_____no_output_____print(image_RGB.GetNumberOfComponentsPerPixel())_____no_output_____

For certain file types such as DICOM, additional information about the image is contained in the meta-data dictionary._____no_output_____

for key in image.GetMetaDataKeys():
        print("\"{0}\":\"{1}\"".format(key, image.GetMetaData(key)))_____no_output_____

## Accessing Pixels

There are the member functions ``GetPixel`` and ``SetPixel`` which provides an ITK-like interface for pixel access._____no_output_____

help(image.GetPixel)_____no_output_____print(image.GetPixel(0, 0, 0))
image.SetPixel(0, 0, 0, 1)
print(image.GetPixel(0, 0, 0))_____no_output_____print(image[0,0,0])
image[0,0,0] = 10
print(image[0,0,0])_____no_output_____

## Conversion between numpy and SimpleITK_____no_output_____

nda = sitk.GetArrayFromImage(image)
print(nda)_____no_output_____help(sitk.GetArrayFromImage)_____no_output_____# Get a view of the image data as a numpy array, useful for display
nda = sitk.GetArrayViewFromImage(image)_____no_output_____nda = sitk.GetArrayFromImage(image_RGB)
img = sitk.GetImageFromArray(nda)
img.GetSize()_____no_output_____help(sitk.GetImageFromArray)_____no_output_____img = sitk.GetImageFromArray(nda, isVector=True)
print(img)_____no_output_____

## The order of index and dimensions need careful attention during conversion

ITK's Image class does not have a bracket operator. It has a GetPixel which takes an ITK Index object as an argument, which is ordered as ``(x,y,z)``. This is the convention that SimpleITK's Image class uses for the GetPixel method and slicing operator as well. In numpy, an array is indexed in the **opposite** order ``(z,y,x)``. Also note that the access to channels is different. In SimpleITK you do not access the channel directly, rather the pixel value representing all channels for the specific pixel is returned and you then access the channel for that pixel. In the numpy array you are accessing the channel directly. _____no_output_____

import numpy as np

multi_channel_3Dimage = sitk.Image([2,4,8], sitk.sitkVectorFloat32, 5)
x = multi_channel_3Dimage.GetWidth() - 1
y = multi_channel_3Dimage.GetHeight() - 1
z = multi_channel_3Dimage.GetDepth() - 1
multi_channel_3Dimage[x,y,z] = np.random.random(multi_channel_3Dimage.GetNumberOfComponentsPerPixel())

nda = sitk.GetArrayFromImage(multi_channel_3Dimage)

print("Image size: " + str(multi_channel_3Dimage.GetSize()))
print("Numpy array size: " + str(nda.shape))

# Notice the index order and channel access are different:
print("First channel value in image: " + str(multi_channel_3Dimage[x,y,z][0]))
print("First channel value in numpy array: " + str(nda[z,y,x,0]))_____no_output_____

### Are we still dealing with Image, because I haven't seen one yet...

While SimpleITK does not do visualization, it does contain a built in ``Show`` method. This function writes the image out to disk and than launches a program for visualization. By default it is configured to use ImageJ, because it is readily supports all the image types which SimpleITK has and load very quickly. However, it's easily customizable by setting environment variables._____no_output_____

sitk.Show(image)_____no_output_____sitk.Show?_____no_output_____

By converting into a numpy array, matplotlib can be used for visualization for integration into the scientific python environment._____no_output_____

%matplotlib inline
import matplotlib.pyplot as plt_____no_output_____z = 0
slice = sitk.GetArrayViewFromImage(image)[z,:,:]
plt.imshow(slice)_____no_output_____


# Notebook from lptolik/lptolik.github.io
Path: markdown_generator/PubsFromBib.ipynb

# Publications markdown generator for academicpages

Takes a set of bibtex of publications and converts them for use with [academicpages.github.io](academicpages.github.io). This is an interactive Jupyter notebook ([see more info here](http://jupyter-notebook-beginner-guide.readthedocs.io/en/latest/what_is_jupyter.html)).

The core python code is also in `pubsFromBibs.py`.
Run either from the `markdown_generator` folder after replacing updating the publist dictionary with:
* bib file names
* specific venue keys based on your bib file preferences
* any specific pre-text for specific files
* Collection Name (future feature)

TODO: Make this work with other databases of citations,
TODO: Merge this with the existing TSV parsing solution_____no_output_____

<code>
from pybtex.database.input import bibtex
import pybtex.database.input.bibtex
from time import strptime
import string
import html
import os
import re_____no_output_____#todo: incorporate different collection types rather than a catch all publications, requires other changes to template
publist = {
    "proceeding": {
        "file" : "my_proceedings.bib",
        "venuekey": "booktitle",
        "venue-pretext": "In the proceedings of ",
        "collection" : {"name":"publications",
                        "permalink":"/publication/"}

    },
    "journal":{
        "file": "my_pubs.bib",
        "venuekey" : "journal",
        "venue-pretext" : "",
        "collection" : {"name":"publications",
                        "permalink":"/publication/"}
    }
}_____no_output_____html_escape_table = {
    "&": "&",
    '"': """,
    "'": "'"
    }

def html_escape(text):
    """Produce entities within text."""
    return "".join(html_escape_table.get(c,c) for c in text)_____no_output_____for pubsource in publist:
    parser = bibtex.Parser()
    bibdata = parser.parse_file(publist[pubsource]["file"])

    #loop through the individual references in a given bibtex file
    for bib_id in bibdata.entries:
        #reset default date
        pub_year = "1900"
        pub_month = "01"
        pub_day = "01"

        b = bibdata.entries[bib_id].fields

        try:
            pub_year = f'{b["year"]}'

            #todo: this hack for month and day needs some cleanup
            if "month" in b.keys():
                if(len(b["month"])<3):
                    pub_month = "0"+b["month"]
                    pub_month = pub_month[-2:]
                elif(b["month"] not in range(12)):
                    tmnth = strptime(b["month"][:3],'%b').tm_mon
                    pub_month = "{:02d}".format(tmnth)
                else:
                    pub_month = str(b["month"])
            if "day" in b.keys():
                pub_day = str(b["day"])

            pub_date = pub_year+"-"+pub_month+"-"+pub_day

            #strip out {} as needed (some bibtex entries that maintain formatting)
            clean_title = b["title"].replace("{", "").replace("}","").replace("\\","").replace(" ","-")

            url_slug = re.sub("\\[.*\\]|[^a-zA-Z0-9_-]", "", clean_title)
            url_slug = url_slug.replace("--","-")

            md_filename = (str(pub_date) + "-" + url_slug + ".md").replace("--","-")
            html_filename = (str(pub_date) + "-" + url_slug).replace("--","-")

            #Build Citation from text
            citation = ""

            #citation authors - todo - add highlighting for primary author?
            for author in bibdata.entries[bib_id].persons["author"]:
                citation = citation+" "+author.first_names[0]+" "+author.last_names[0]+", "

            #citation title
            citation = citation + "\"" + html_escape(b["title"].replace("{", "").replace("}","").replace("\\","")) + ".\""

            #add venue logic depending on citation type
            venue = publist[pubsource]["venue-pretext"]+b[publist[pubsource]["venuekey"]].replace("{", "").replace("}","").replace("\\","")

            citation = citation + " " + html_escape(venue)
            citation = citation + ", " + pub_year + "."

            ## YAML variables
            md = "---\ntitle: \""   + html_escape(b["title"].replace("{", "").replace("}","").replace("\\","")) + '"\n'

            md += """collection: """ +  publist[pubsource]["collection"]["name"]

            md += """\npermalink: """ + publist[pubsource]["collection"]["permalink"]  + html_filename

            note = False
            if "note" in b.keys():
                if len(str(b["note"])) > 5:
                    md += "\nexcerpt: '" + html_escape(b["note"]) + "'"
                    note = True

            md += "\ndate: " + str(pub_date)

            md += "\nvenue: '" + html_escape(venue) + "'"

            url = False
            if "url" in b.keys():
                if len(str(b["url"])) > 5:
                    md += "\npaperurl: '" + b["url"] + "'"
                    url = True

            md += "\ncitation: '" + html_escape(citation) + "'"

            md += "\n---"

            ## Markdown description for individual page
            if note:
                md += "\n" + html_escape(b["note"]) + "\n"

            if url:
                md += "\n[Access paper here](" + b["url"] + "){:target=\"_blank\"}\n"
            else:
                md += "\nUse [Google Scholar](https://scholar.google.com/scholar?q="+html.escape(clean_title.replace("-","+"))+"){:target=\"_blank\"} for full citation"

            md_filename = os.path.basename(md_filename)

            with open("../_publications/" + md_filename, 'w') as f:
                f.write(md)
            print(f'SUCESSFULLY PARSED {bib_id}: \"', b["title"][:60],"..."*(len(b['title'])>60),"\"")
        # field may not exist for a reference
        except KeyError as e:
            print(f'WARNING Missing Expected Field {e} from entry {bib_id}: \"', b["title"][:30],"..."*(len(b['title'])>30),"\"")
            continue
WARNING Missing Expected Field 'booktitle' from entry Deeva:2015176: " 176 {{Differences}} of the Act ... "
SUCESSFULLY PARSED FUCIC:2011Chapter: " Chapter 13. {{Biomarkers}} of {{Exposure}} and {{Effect}}: { ... "
SUCESSFULLY PARSED Kamzolova:2008ELECTROSTATIC: " {{ELECTROSTATIC PROPERTIES OF E}}. {{COLI GENOME DNA}}  "
WARNING Missing Expected Field 'booktitle' from entry Kondratyev:2015195: " 195 {{In}} Silico Design and V ... "
WARNING Missing Expected Field 'booktitle' from entry Sorokin:201537: " 37 {{Spatial}} Organisation of ... "
WARNING Missing Expected Field 'booktitle' from entry Temlyakova:201370: " 70 {{Electrostatic}} Propertie ... "
SUCESSFULLY PARSED Fursova:2021Changes: " Changes in the {{Microbiome}} of {{Milk}} in {{Cows}} with { ... "
SUCESSFULLY PARSED Kamzolova:2005Electrostatic: " Electrostatic Potentials of {{E}}.Coli Genome {{DNA}}  "
SUCESSFULLY PARSED Kamzolova:2011Electrostatic: " Electrostatic {{Properties}} of {{Bacteriophage T7 Early Pro ... "
SUCESSFULLY PARSED Kenney:2015Dynamics: " Dynamics of Elongation Factor 2 Kinase Regulation in Cortica ... "
SUCESSFULLY PARSED Khilyas:2017Comparative: " Comparative {{Metagenomic Analysis}} of {{Electrogenic Micro ... "
SUCESSFULLY PARSED Orakov:2018ASAR: " {{ASAR}}: Visual Analysis of Metagenomes in {{R}}.  "
SUCESSFULLY PARSED Orlov:2018Comparative: " Comparative {{Analysis}} of {{Mycoplasma}} Gallisepticum {{v ... "
SUCESSFULLY PARSED Pekov:2019Inline: " Inline Cartridge Extraction for Rapid Brain Tumor Tissue Ide ... "
SUCESSFULLY PARSED Polozov:1999Electrostatic: " Electrostatic Potentials of {{DNA}}. {{Comparative}} Analysi ... "
SUCESSFULLY PARSED Ryasik:2016Classification: " Classification of {{E}}. Coli Promoters with {{DNA}} Open St ... "
SUCESSFULLY PARSED Ryasik:2018Bacterial: " Bacterial Promoter Prediction: {{Selection}} of Dynamic and  ... "
SUCESSFULLY PARSED SGKamzolova:2014Electrostatic: " Electrostatic Map of {{T7 DNA}}: Comparative Analysis of Fun ... "
SUCESSFULLY PARSED Sobolev:1999Automated: " Automated Analysis of Interatomic Contacts in Proteins.  "
SUCESSFULLY PARSED Sokolov:2021Comparative: " Comparative {{Analysis}} of {{Milk Microbiomes}} and {{Their ... "
SUCESSFULLY PARSED Sorokin:2006Electrostatic: " Electrostatic Properties of Promoter Recognized by {{E}}. Co ... "
SUCESSFULLY PARSED Sorokin:2012userdefined: " A User-Defined Data Type for the Storage of Time Series Data ... "
SUCESSFULLY PARSED Sorokin:2017Feature: " Feature Selection Algorithm for Spray-from-Tissue Mass Spect ... "
SUCESSFULLY PARSED Sorokina:2013simulator: " A Simulator for Spatially Extended Kappa Models.  "
SUCESSFULLY PARSED Szydlowski:2020Evolutionary: " Evolutionary {{Dynamics}} of {{Microbial Communities}} in {{ ... "
SUCESSFULLY PARSED Thiele:2013communitydriven: " A Community-Driven Global Reconstruction of Human Metabolism  "
SUCESSFULLY PARSED Thomas:2009fragile: " A Fragile Metabolic Network Adapted for Cooperation in the S ... "
SUCESSFULLY PARSED Zhvansky:2017Highresolution: " High-Resolution Mass Spectra Processing for the Identificati ... "
SUCESSFULLY PARSED Zhvansky:2020Assessment: " Assessment of Variation of Inline Cartridge Extraction Mass  ... "
SUCESSFULLY PARSED Zhvansky:2021Comparison: " Comparison of {{Dimensionality Reduction Methods}} in {{Mass ... "
SUCESSFULLY PARSED Zhvansky:2021Interactive: " Interactive {{Estimation}} of {{Heterogeneity}} from {{Mass  ... "
(pubsource,bib_id,author)_____no_output_____author.first_names_____no_output_____author.last_names_____no_output_____
</code>