# Language Classification with Naive Bayes in Python

## Recommended Prerequisites for Successful Completion
* Intermediate level understanding of Python 3+ (e.g. list and dictionary comprehension)
* Basics of machine learning (e.g. the distinction between training and validation data)
* Mathematical probability (e.g. understanding Bayes' Theorem at a basic level)

## Project Outline
Introduction (#intro)

(#task1): Exploratory Data Analysis + Visualization

(#task2): Data Cleaning and Preprocessing

(#task3): Naive Bayes Model Introduction and Training

(#task4): Highlighting Problems with Basic Model and Simple Fixes

(#task5): Advanced Approach to Further Improve Performance

```python
import matplotlib
%matplotlib inline
%config InlineBackend.figure_format = 'svg'
import matplotlib.pyplot as plt
plt.style.use('ggplot')

from tqdm import tqdm_notebook
import numpy as np
import string

from collections import defaultdict

from sklearn.metrics import f1_score
from sklearn.naive_bayes import MultinomialNB
from sklearn.feature_extraction.text import CountVectorizer

import joblib
import pickle as pkl
from sklearn.metrics import confusion_matrix
import seaborn as sns

```

# Introduction

## [Slovak Wikipedia Entry](https://sk.wikipedia.org/wiki/Jazveč%C3%ADk)
Mnohí ľudia, ktorí vidia na ulici jazvečíka s podlhovastým telom vôbec nevedia o tom, že tento malý štvornohý a veľmi obľúbený spoločník je pri dobrom výcviku obratným, vynikajúcim a spoľahlivým poľovným psom. Ako poľovný pes je mnohostranne využiteľný, okrem iného ako durič na brlohárenie. Králičí jazvečík sa dokáže obratne pohybovať v králičej nore. S inými psami a deťmi si nie vždy rozumie.

## [Czech Wikipedia Entry](https://cs.wikipedia.org/wiki/Jezevč%C3%ADk)
Úplně první zmínky o psech podobných dnešním jezevčíkům nacházíme až ve Starém Egyptě, kde jsou vyobrazeni na soškách a rytinách krátkonozí psi s dlouhým hřbetem a krátkou srstí. Jednalo se ale o neustálený typ bez ustáleného jména. Další zmínky o jezevčících nacházíme až ve 14 - 15. století. Jedná se o psa, který se nejvíce podobá dnešnímu typu hladkosrstého standardního jezevčíka.

## [English Wikipedia Entry](https://en.wikipedia.org/wiki/Dachshund)
While classified in the hound group or scent hound group in the United States and Great Britain, the breed has its own group in the countries which belong to the Fédération Cynologique Internationale (World Canine Federation). Many dachshunds, especially the wire-haired subtype, may exhibit behavior and appearance that are similar to that of the terrier group of dogs.

# Task 1: Data Exploration and Visualization

```python
def open_file(filename):
    with open(filename, encoding="utf8") as f:
        data = f.readlines()
    return data
```

```python
data_raw = dict()
data_raw['sk'] = open_file('Data/Sentences/train_sentences.sk')
data_raw['cs'] = open_file('Data/Sentences/train_sentences.cs')
data_raw['en'] = open_file('Data/Sentences/train_sentences.en')
```

```python
def show_statistics(data):
    for language, sentences in data.items():

        number_of_sentences = 0
        number_of_words = 0
        number_of_unique_words = 0
        sample_extract = ''

        # take a few minutes to try populate these variables

        # here is a hint -- word_list breaks the collections of sentences into a list of words
        word_list = ' '.join(sentences).split()

        print(f'Language: {language}')
        print('-----------------------')
        print(f'Number of sentences\t:\t {number_of_sentences}')
        print(f'Number of words\t\t:\t {number_of_words}')
        print(f'Number of unique words\t:\t {number_of_unique_words}')
        print(f'Sample extract\t\t:\t {sample_extract}...\n')
```

```python
show_statistics(data_raw)
```

    Language: sk
    -----------------------
    Number of sentences	:	 0
    Number of words		:	 0
    Number of unique words	:	 0
    Sample extract		:	 ...

    Language: cs
    -----------------------
    Number of sentences	:	 0
    Number of words		:	 0
    Number of unique words	:	 0
    Sample extract		:	 ...

    Language: en
    -----------------------
    Number of sentences	:	 0
    Number of words		:	 0
    Number of unique words	:	 0
    Sample extract		:	 ...

# Task 2: Data Cleaning and Preprocessing

```python
def preprocess(text):
    '''
    Removes punctuation and digits from a string, and converts all characters to lowercase.
    Also clears all \n and hyphens (splits hyphenated words into two words).

    '''

    preprocessed_text = text.lower().replace('-', ' ')

    translation_table = str.maketrans('\n', ' ', string.punctuation+string.digits)

    preprocessed_text = preprocessed_text.translate(translation_table)

    return preprocessed_text
```

```python
data_preprocessed = {k: [preprocess(sentence) for sentence in v] for k, v in data_raw.items()}
```

```python
show_statistics(data_preprocessed)
```

    Language: sk
    -----------------------
    Number of sentences	:	 0
    Number of words		:	 0
    Number of unique words	:	 0
    Sample extract		:	 ...

    Language: cs
    -----------------------
    Number of sentences	:	 0
    Number of words		:	 0
    Number of unique words	:	 0
    Sample extract		:	 ...

    Language: en
    -----------------------
    Number of sentences	:	 0
    Number of words		:	 0
    Number of unique words	:	 0
    Sample extract		:	 ...

# Task 3: The Naive Bayes Model

**Bayes' Theorem**

\begin{equation}
P(A | B)=\frac{P(B | A) \times P(A)}{P(B)}
\end{equation}

Now, let's translate this theory into our specific problem. In our case, where we want to categorise a sentence `my name is Ari` into one of `sk`, `cs`, or `en`, the following are the probabilities we want to determine.

\begin{equation}
P(\text {sk} | \text {my name is Ari})=\frac{P(\text {my name is Ari} | \text {sk}) \times P(\text {sk})}{P(\text {my name is Ari})}
\end{equation}

\begin{equation}
P(\text {cs} | \text {my name is Ari})=\frac{P(\text {my name is Ari} | \text {cs}) \times P(\text {cs})}{P(\text {my name is Ari})}
\end{equation}

\begin{equation}
P(\text {en} | \text {my name is Ari})=\frac{P(\text {my name is Ari} | \text {en}) \times P(\text {en})}{P(\text {my name is Ari})}
\end{equation}

## Unseen Data

Since we assume conditional independence across our features, our numerator term for any of the above equations can be broken into the following.

\begin{equation}
P(\text {my name is Ari} | \text {en}) = P(\text {my} | \text {en}) \times P(\text {name} | \text {en}) \times P(\text {is} | \text {en}) \times P(\text {Ari} | \text {en})
\end{equation}

## Vectorizing Training Data

```python
def get_data(data):
    st , label = [],[]
    for k, v in data.items():
        for sentences in v:
            st.append(sentences)
            label.append(k)
    return st, label
```

```python
sentences_train, y_train = get_data(data_preprocessed)

```

```python
vectorizer = CountVectorizer()
```

```python
X_train = vectorizer.fit_transform(sentences_train)
```

```python
X_train
```

    <210x2208 sparse matrix of type '<class 'numpy.int64'>'
    	with 3867 stored elements in Compressed Sparse Row format>

## Initializing Model Parameters and Training

```python
naive_classifier = MultinomialNB()
naive_classifier.fit(X_train, y_train)
```

    MultinomialNB(alpha=1.0, class_prior=None, fit_prior=True)

## Vectorizing Validation Data and Evaluating Model

```python
data_val = dict()
data_val['sk'] = open_file('Data/Sentences/val_sentences.sk')
data_val['cs'] = open_file('Data/Sentences/val_sentences.cs')
data_val['en'] = open_file('Data/Sentences/val_sentences.en')

data_val_preprocessed = {k: [preprocess(sentence) for sentence in v] for k, v in data_val.items()}
```

```python
sentences_val, y_val = get_data(data_val_preprocessed)
```

```python
X_val = vectorizer.transform(sentences_val)
```

```python
predictions = naive_classifier.predict(X_val)
```

```python

def plot_confusion_matrix(labels , y_pred,classes):
    cm=confusion_matrix(labels,y_pred,classes)

    # ploting the matrix in form of heat map

    sns.heatmap(cm)
```

```python
plot_confusion_matrix(y_val, predictions, ['sk', 'cs', 'en'])
```

```python
f1_score(y_val, predictions, average='weighted')
```

    0.6149824401040264

# Simple Adjustments and Highlighting Model Shortcomings

```python
naive_classifier = MultinomialNB(alpha=0.0001, fit_prior=False)
naive_classifier.fit(X_train, y_train)

predictions = naive_classifier.predict(X_val)

plot_confusion_matrix(y_val, predictions, ['sk', 'cs', 'en'])
```

```python
f1_score(y_val, predictions, average='weighted')
```

    0.8368507601649364

# Using Subwords to Shift Perspective

**Dummy Dataset**

playing ; eating ; play ; reads ; tea

**Step 1**

Break each word into characters

playing > p l a y i n g

**Step 2**

Find common character sequences

ea, ing, play

**Step 3**

Convert dataset using these subwords into

play ing ; ea t ing ; play ; r ea d s ; t ea

```python
# taken from https://arxiv.org/abs/1508.07909

import re, collections
def get_stats(vocab):
    pairs = collections.defaultdict(int)
    for word, freq in vocab.items():
        symbols = word.split()
        for i in range(len(symbols)-1):
            pairs[symbols[i],symbols[i+1]] += freq
    return pairs

def merge_vocab(pair, v_in):
    v_out = {}
    bigram = re.escape(' '.join(pair))
    p = re.compile(r'(?<!\S)' + bigram + r'(?!\S)')
    for word in v_in:
        w_out = p.sub(''.join(pair), word)
        v_out[w_out] = v_in[word]
    return v_out
```

```python
def get_vocab(data):

    words = []
    for sentence in data:
        words.extend(sentence.split())

    vocab = defaultdict(int)
    for word in words:
        vocab[' '.join(word)] += 1

    return vocab
```

```python
vocab = get_vocab(sentences_train)
```

```python
# also taken from original paper
for i in range(100):
    pairs = get_stats(vocab)
    best = max(pairs, key=pairs.get)
    vocab = merge_vocab(best, vocab)
```

```python
merges = defaultdict(int)
for k, v in vocab.items():
    for subword in k.split():
        if len(subword) >= 2:
            merges[subword] += v
```

```python
merge_ordered = sorted(merges, key=merges.get, reverse=True)
```

```python
pkl.dump(merge_ordered, open('Data/Auxiliary/merge_ordered.pkl', 'wb'))
```

```python
def split_into_subwords(text):
    merges = pkl.load(open('Data/Auxiliary/merge_ordered.pkl', 'rb'))
    subwords = []
    for word in text.split():
        for subword in merges:
            subword_count = word.count(subword)
            if subword_count > 0:
                word = word.replace(subword, ' ')
                subwords.extend([subword]*subword_count)
    return ' '.join(subwords)
```

```python
split_into_subwords('this is ari here')
```

    'is th is ar re'

```python
data_preprocessed_subwords = {k: [split_into_subwords(sentence) for sentence in v] for k, v in data_preprocessed.items()}
```

```python
show_statistics(data_preprocessed_subwords)
```

    Language: sk
    -----------------------
    Number of sentences	:	 0
    Number of words		:	 0
    Number of unique words	:	 0
    Sample extract		:	 ...

    Language: cs
    -----------------------
    Number of sentences	:	 0
    Number of words		:	 0
    Number of unique words	:	 0
    Sample extract		:	 ...

    Language: en
    -----------------------
    Number of sentences	:	 0
    Number of words		:	 0
    Number of unique words	:	 0
    Sample extract		:	 ...

```python
data_train_subwords = []
for sentence in sentences_train:
    data_train_subwords.append(split_into_subwords(sentence))
```

```python
data_val_subwords = []
for sentence in sentences_val:
    data_val_subwords.append(split_into_subwords(sentence))
```

```python
vectorizer = CountVectorizer()
```

```python
X_train = vectorizer.fit_transform(data_train_subwords)
X_val = vectorizer.transform(data_val_subwords)
```

```python
naive_classifier = MultinomialNB(fit_prior=False)
naive_classifier.fit(X_train, y_train)
```

    MultinomialNB(alpha=1.0, class_prior=None, fit_prior=False)

```python
predictions = naive_classifier.predict(X_val)
```

```python
plot_confusion_matrix(y_val, predictions, ['sk', 'cs', 'en'])
```

```python
f1_score(y_val, predictions, average='weighted')
```

    0.8456381060126386

```python

```


# GaiaLab - Introductory infos

Toy model in which the snapshots are taken continuously, i.e. not at discrete time steps.

---

## Content:

- [#1.-Scanner-Object](#1.-Scanner-Object)
- [#2.-Source-detection](#2.-Source-detection)
    - Sirio
    - Vega
    - Proxima Centauri
- [#3.-For-more](#3.-For-more)

---

## Few definitions: (from wikipedia:)
* Attitude:   https://en.wikipedia.org/wiki/Attitude_control
* Parallax:   https://en.wikipedia.org/wiki/Parallax
* B-Spline:   https://en.wikipedia.org/wiki/B-spline
* Quaternion: https://en.wikipedia.org/wiki/Quaternion
* CCD (Charge-Coupled Device): https://en.wikipedia.org/wiki/Charge-coupled_device
* BCRS and GCRS (Barycentric/Geocentric Celestial Reference System): https://en.wikipedia.org/wiki/Barycentric_celestial_reference_system
* Some other things about references frames: https://www.gaia.ac.uk/science/astronomical-coordinate-systems
* TCB (Barycentric Coordinate Time): https://en.wikipedia.org/wiki/Barycentric_Coordinate_Time
* Topocentric function: https://en.wikipedia.org/wiki/Horizontal_coordinate_system

# Abstract

This notebook explains the wotking principle of the scanner object, together with auxiliary source and satellite objects

```python
# To use interact -- IPython widget
from __future__ import print_function
from ipywidgets import interact, interactive, fixed, interact_manual
import ipywidgets as widgets

# Generic module imports import
from IPython.display import Image
import sys
import numpy as np
from matplotlib import pyplot as plt

# append to path the folder that contains the analytic scanner and import local files
sys.path.append('../../GaiaLab/scan/analytic_scanner')

import constants as const

from scanner import Scanner
from satellite import Satellite
from source import Source

import analytic_plots as plots
from agis_functions import scanning_direction
from agis_functions import get_angular_FFoV_PFoV
from agis_functions import *

# Ipython magics
%load_ext autoreload
%autoreload 2
# for pseudo interactive plots
# %matplotlib notebook

# Set some user-specific variables
MAIN_FOLDER = '../../' # relative path to project folder
FIG_FOLDER = '../figures/' # relative path to figure
```

**About the frames we will use**

Here is an image representing the frames:

Figure 1. Definition of angles $\epsilon$, $\lambda$, $\nu$, $\xi$, and $\Omega$ in the nominal scanning law.

The nominal scanning law of Gaia is described by the following angles:
* $\epsilon$: obliquity of equator. This is a $\it{constant}$ chosen to be 23º 26' 21.448''
* $\xi$: revolving angle. At any time the z axis is at this $\it{constant}$ angle from $\vec{s}$. For Gaia, the current choice is 55º.
* $\lambda_{s}(t)$: nominal longitud of the sun in the ecliptic plane. This is chosen to have a constant speed: $\dot{\lambda}(t) = \frac{2\pi}{365}$
* $\nu(t)$: revolving phase. Gives a nearly constant precession rate.
* $\Omega(t)$: spin phase.

Frames of referenced used:
* Interial Frame: $\textbf{G} = \big[ \textbf{l},\textbf{m},\textbf{n}\big]$ gaia non-turning wrt to sun reference system
* Celestial frame of reference (ort): $\textbf{N} = \big[ \textbf{l},\textbf{j},\textbf{k}\big]$ Sun reference
* Instrument triad frame (ort): $\textbf{Z} = \big[ \textbf{x},\textbf{y},\textbf{z}\big]$  Gaia reference turning
* Ecliptic triad $\textbf{E}= \big[ \textbf{l},\textbf{j},\textbf{k}\big]$

Given by:
$$
\textbf{k} = \textbf{n}cos(\epsilon) - \textbf{m} sin(\epsilon) \\
\textbf{j} = \textbf{m}cos(\epsilon) + \textbf{n} sin(\epsilon) \\ \textbf{s}=\textbf{l}cos(\lambda_{s}(t)) + \textbf{j} sin(\lambda_{s}(t)
$$

The total inertial rotation of the telescope is therefore:
$$
\begin{equation}
\vec{\omega} = \textbf{k} \dot{\lambda_{s}} + \textbf{s} \dot{\nu} + \textbf{z} \dot{\Omega}
\end{equation}
$$

The precessional motion of the z-axis is:

$$
\begin{equation}
\dot{z} = \omega \times z= \big( k \times z\big)\dot{\lambda_{s}} + \big(s \times z\big)\dot{\nu}
\end{equation}
$$

And if we consider the rate of change of the z-axis wrt time to be a constant:
$$
\begin{equation}
\frac{dz}{dt}\frac{dt}{d\lambda_{s}} = \frac{dz}{dt} \dot{\lambda_{s}}^{-1} = S
\end{equation}$$

And keeping the condition $ \|\big( k \times z \big)\|^{2} = 1 - sin^{2}(\xi)sin^{2}(\nu)$ then the following ODE for $\nu$ is obtained:

$$
\begin{equation}
\dot{\nu} = \dot{\lambda_{s}}\frac{\sqrt{S^{2} - cos^{2}(\nu)} + cos(\xi)sin(\nu)}{sin(\xi)}
\end{equation}
$$

Taking the positive sign of the square root implying increasing $\nu$.
Then, for constant inertial spin rate about the z axis using the equation for the inertial rotation $\omega$, we obtain:
$$
\begin{equation}
\dot{\Omega} = \omega_{z} - \dot{\nu}cos\xi - \dot\lambda_{s}sin\xi sin\nu
\end{equation}
$$

To see how does the longitude and latitude of the z axis changes with time, according to Figure 1, the geometry gives the following equations in the ecliptic plane:
$$
\begin{equation}
\lambda_{z}(t) = \lambda_{s}(t) + arctan\big[ tan\xi cos\nu(t) \big]. \\
\beta_{z}(t) = arcsin \big[ sin\xi sin\nu(t) \big].
\end{equation}
$$

Frames of referenced used:
* Celestial Frame: $\textbf{G} = \big[ \textbf{l},\textbf{m},\textbf{n}\big]$
* Instrument Frame: $\textbf{Z} = \big[ \textbf{x},\textbf{y},\textbf{z}\big]$
* Barycentric Reference System $\textbf{BCRS}= \big[ \textbf{l'},\textbf{m'},\textbf{n'}\big]$

The BCRS is a frame of reference that has its axis all parallel to G-Frame, but that is displaced by 1AU. That is to say, it is the frame centered at the sun.

# 1. Construct Object

```python
t_init = 0  # [days] initial time of satellite position wrt epoch J2000. This is also the time followed by the scanner.
t_end = 365*5  # [days] end time at which we compute satellite position.
my_dt = 1/24 # [days] time step for the computation of the satellite attitude

# Create the Satellite object
gaia = Satellite(t_init, t_end, my_dt)

# Create the Scanner object
scanner = Scanner()
```

### 1.1 Check properties of objects

##### 1.1.1 properties of the Satellite

Plotting the time evolution of the attitude components, begining at $t=0$ up to $t=80$ days.

```python
# plot the attitude of the satellite
plots.plot_attitude(gaia, ti=0, tf=80, n_points=1000, style='-')
```

```python

```

Plotting the scanner positions:

```python
myTime = 50
plots.plot_3D_scanner_pos(gaia, 'X', 0, myTime, 1000, elevation=30, azimuth=30)
plots.plot_3D_scanner_pos(gaia, 'Z', 0, myTime, 1000)
def my_func(elev, azim):
    plots.plot_3D_scanner_pos(gaia, 'X', 0, myTime, 1000, elevation=elev, azimuth=azim)
interact(my_func, elev=widgets.IntSlider(min=0,max=90,step=1,value=20),
        azim=widgets.IntSlider(min=0,max=90,step=1,value=20))
# x,y,z wrt to lmn reference
```

 ##### 1.1.2 Scanner properties
 We just check some of the properties of the scanner:

```python
print('Size of the field of view of the scanner: ', np.degrees(scanner.zeta_limit),'°')
print('Is the scanner using two telescope in a gaia-like fascion? ', scanner.double_telescope)
```

    Size of the field of view of the scanner:  0.5 °
    Is the scanner using two telescope in a gaia-like fascion?  True

# 2. Source detection

In this section we create example stars from real data. The Source object takes as inputs:

> Source('name', $\alpha$, $\delta$, parallax, $\mu_{\alpha}$, $\mu_{\delta}$, $\mu_{r}$)

with units: [string, deg, deg, mas, mas/yr, mas/yr, km/s]

### 2.1 Create source

```python
# Comments / uncomment lines to play with different sources

source = Source("sirio", 101.28, -16.7161, 379.21, -546.05, -1223.14, -7.6)
# source = Source("vega", 279.2333, 38.78, 128.91, 201.03, 286.23, -13.9)
# source = Source("proxima",217.42, -62, 768.7, 3775.40, 769.33, 21.7)
```

##### 2.1.1 Visualize the evolution of the source in time

```python
# With respect to equatorial coordinates:
fig = plots.plot_star_trajectory(source, gaia);
# fig.show()
```

```python
fig, ax = plots.plot_scanner_position_over_source(source=source, sat=gaia, t_init=0, t_end=1,
                                                  num_points_of_discretization = 10000);
```

### 2.2 Scan source

```python
time_taken_for_scan = scanner.scan(gaia, source, t_init, t_end)
print('Scan took: ', time_taken_for_scan, 'seconds')
print('Source observed ', len(scanner.obs_times), 'times')
```

    Scan took:  1.22025728225708 seconds
    Source observed  75 times

##### 2.2.1 Visualize scan

```python
# Get fields angle from scanner:
scanner.compute_angles_eta_zeta(gaia, source)

```

```python
as_,ds_ = ([], [])
for i, t in enumerate(scanner.obs_times):

    attitude = gaia.func_attitude(t)
    phi = scanner.eta_scanned[i]
    zeta = scanner.zeta_scanned[i]
    a, d = field_angles_to_alpha_delta(phi, zeta, attitude)
    as_.append(a)
    ds_.append(d)
    print(a,d)
plt.figure()
as_ = ft.zero_to_two_pi_to_minus_pi_pi(np.array(as_))
ds_ = ft.transform_twoPi_into_halfPi(np.array(ds_))
plt.plot(as_,ds_,'+')
```

```python
fig = plots.plot_star_trajectory(source, gaia, obs_times=scanner.obs_times, equatorial=False,
                                 show_scanning_directions=False);
```

Where the red dots represent the position of the star at the time of observation.

##### 2.2.2 Quantitative results

Check the deviation from 0 in the along scan direction:

```python
print('Scan error in the along scan direction: ', scanner.scanner_error(), '[rads], \nor equivalently,',
       scanner.scanner_error()/const.rad_per_mas, '[mas]')
```

    Scan error in the along scan direction:  -8.979896929409987e-14 [rads],
    or equivalently, -1.852236700263647e-05 [mas]

Check that all the observations are inside the limitations of the Field of View (FoV) which is [-0.5°, 0.5°]:

```python
# Check that all the observations are in the field of view:
plt.figure()
plt.title('Across scan angle of the observations')
plt.plot(scanner.zeta_scanned, '+', label='Observation')
plt.hlines(np.radians([-0.5, 0.5]), xmin=0, xmax=len(scanner.obs_times), label='| 0.5°|')
plt.xlim(0, len(scanner.obs_times))
plt.xlabel('Index of the observation')
plt.ylabel('$\zeta$ [rads]')
plt.legend()
plt.show();
```

Where $\zeta$ is the Across scan angle of the observation.

# 3. Conclusion

Thank you for reading! We hope tha tthis notebook could help you understand the model and we hope you had fun discovering it!
For more about this project, visit the others notebooks as well as the gaia website.

_For any errors or remarks, don't hesitate to contact us!_

```python

```

```python

```

```python

```

```python

```

# Tests

```python
import constants as const
import frame_transformations as ft
equatorial = True
alphas_sol, deltas_sol = ([], [])  # coordinates of the founds sources
times_sol = []
sat=gaia
obs_times = scanner.obs_times
for t in obs_times:
    alpha_obs, delta_obs, delta_alpha_dx_mas, delta_delta_mas = source.topocentric_angles(sat, t)
    times_sol.append(t/const.days_per_year + 2000)  # +2000 do the fact that the reference epoch is J2000
    if equatorial is True:
        alphas_sol.append(alpha_obs)
        deltas_sol.append(delta_obs)
    else:
        alphas_sol.append(delta_alpha_dx_mas)
        deltas_sol.append(delta_delta_mas)

# Convert all in mas:
if equatorial is True:
    if obs_times:
        alphas_sol = np.array(alphas_sol) / const.rad_per_mas
        deltas_sol = np.array(deltas_sol) / const.rad_per_mas
```

```python
np.sort(obs_times)
```

    array([   9.54965349,    9.62360949,   41.51333726,   41.58729534,
             89.15177884,   89.22574167,   89.40178235,   89.47574443,
            100.3997511 ,  100.47371282,  100.64975141,  100.72371389,
            149.34471992,  149.52076293,  149.59472506,  215.01009736,
            267.11808211,  267.19204527,  267.36809264,  267.44205434,
            267.61809983,  267.6920601 ,  267.86810376,  267.94206263,
            293.43740771,  293.61344987,  293.68741195,  293.86345559,
            293.9374188 ,  294.11346395,  294.18742831,  294.363475  ,
            294.43744053,  352.54361573,  352.61757383,  391.31145956,
            419.46080196,  419.53475965,  467.15995955,  467.33601494,
            467.4099732 ,  467.58602379,  478.83412302,  478.90808131,
            479.08413362,  479.15809271,  526.53748509,  526.71353878,
            556.43222579,  556.50618798,  594.20535372,  594.38140926,
            654.30956608,  654.38353273,  654.55958575,  654.633551  ,
            654.80960218,  654.88356605,  655.05961542,  655.13357792,
            683.6260836 ,  683.80213668,  683.87609702,  684.05215275,
            684.12611518,  731.48404179,  731.55800951,  772.48564665,
            772.66170136,  844.00856099,  844.08252235,  844.25857877,
            858.5049547 ,  858.57891493,  858.75497464,  858.82893571,
            904.21915111,  904.3952057 ,  938.3517184 ,  938.42568381,
            974.06410084,  974.13806796,  974.31412333, 1036.98997297,
           1037.06394165, 1037.23999814, 1037.31396468, 1069.54637634,
           1069.72243539, 1069.79640185, 1110.41120398, 1110.48517018,
           1153.14654085, 1153.32259928, 1174.06152949, 1174.13549803,
           1220.1706271 , 1220.24459041, 1220.42065477, 1282.56537183,
           1282.63934171, 1320.5833642 , 1320.75942988, 1320.83339318,
           1354.80815324, 1354.98421257, 1355.0581839 , 1355.23424638,
           1417.6578794 , 1417.7318489 , 1489.07503113, 1489.14900105,
           1533.22053456, 1533.29450219, 1550.96422652, 1551.03819142,
           1596.64689116, 1596.82295523, 1619.63192632, 1619.80799253,
           1661.47384654, 1661.54781681, 1704.15626196, 1704.23023244,
           1737.46549893, 1737.64156578, 1737.7155364 , 1737.89160576,
           1737.96557834, 1797.56314728, 1797.63712055])

```python
def transform_twoPi_into_halfPi(deltas):
    deltas = np.array(deltas)
    to_modify_indices = np.where(deltas>np.pi)[0]
    deltas[to_modify_indices] -= 2*np.pi
    return deltas
```

```python
from agis_functions import *
ras0, ras1, decs0, decs1 = ([], [], [], [])
for t in np.linspace(0, t_end, num=1000):
    ra0, dec0, _, _ = np.array(get_angular_FFoV_PFoV(gaia, t))
    ra1, dec1, _, _ = np.array(get_angular_FFoV_PFoV(gaia, t+1/24/4))

    attitude = gaia.func_attitude(t)
    ra0, dec0 = generate_observation_wrt_attitude(attitude)

    ra0, ra1 = ft.zero_to_two_pi_to_minus_pi_pi(np.array([ra0,ra1]))
    dec0, dec1 = transform_twoPi_into_halfPi(np.array([dec0, dec1]))
    ras0.append(ra0)
    ras1.append(ra1)
    decs0.append(dec0)
    decs1.append(dec1)

plt.plot(ras0, decs0,'b,')
# plt.plot(ras1, decs1,'r+')
plt.grid()
```

```python

import helpers as helpers
for i, (t, a, d) in enumerate(zip(np.sort(obs_times), alphas_sol, deltas_sol)):
    if i >3:
        continue
    point = np.array([a, d])

    ra0, dec0, _, _ = np.array(get_angular_FFoV_PFoV(gaia, t))
    ra1, dec1, _, _ = np.array(get_angular_FFoV_PFoV(gaia, t+1/24/60/60))
    ra0, ra1 = ft.zero_to_two_pi_to_minus_pi_pi(np.array([ra0,ra1]))
    dec0, dec1 = transform_twoPi_into_halfPi(np.array([dec0, dec1]))

    vect_1 = np.array([ra1-ra0, dec1-dec0])/const.rad_per_mas

    """plt.plot(ra0, dec0,'b.')
    plt.plot(ra1, dec1,'r.')"""

    att = sat.func_attitude(t)
    point0 = ft.rotate_by_quaternion(att, [0, 0, 0])
    point1 = ft.rotate_by_quaternion(att, [0, 1, 0])

    point0 = ft.vector_to_alpha_delta(point0)
    point1 = ft.vector_to_alpha_delta(point1)

    vect = np.array([point1[0]-point0[0], point1[1]-point0[1]])
    # vect = helpers.normalize(vect)

    vector = scanning_direction(source, sat, t)
    adp = ft.vector_to_adp(vector)
    dir_alpha, dir_delta = ft.vector_to_alpha_delta(vector)
    directions = np.array([dir_alpha, dir_delta])
    # directions = helpers.rescaled_direction((dir_alpha, dir_delta), length)
    to_plot_x = np.array([point[0], point[0]+dir_alpha])
    to_plot_y = np.array([point[1], point[1]+dir_delta])
    # plt.plot(to_plot_x, to_plot_y, 'k-', alpha=0.5)
    # plt.quiver(point[0], point[1], vect[0], vect[1], color=['r'])
    plt.quiver(point[0], point[1], vect_1[0], vect_1[1], color=['b'])
    # plt.quiver(point[0], point[1], directions[0], directions[1], color=['k'])
    # plt.quiver(0, 0, directions[0], directions[1], color=['r'])
```

```python

```

```python

for t in [0, 10, 20, 30, 40, 100, 200, 300, 600, 800]:
    att = sat.func_attitude(t)
    point0 = ft.rotate_by_quaternion(att, [0, 0, 0])
    point1 = ft.rotate_by_quaternion(att, [0, 1, 0])

    point0 = ft.vector_to_alpha_delta(point0)
    point1 = ft.vector_to_alpha_delta(point1)

    vect = np.array([point1[0]-point0[0], point1[1]-point0[1]])

    # vect = helpers.normalize(vect)

    plt.quiver(0, 0, vect[0], vect[1], color=['r'])
plt.show()
```

```python

```