# -*- coding: utf-8 -*-
"""
Created on Fri Apr 11 20:23:10 2025

@author: AKourgli
"""

import numpy as np
import matplotlib.pyplot as plt

# Paramètres
M = 4                # Modulation QPSK (4-PSK)
fc = 1000            # Fréquence porteuse (Hz)
fs = 10000           # Fréquence d'échantillonnage (Hz)
t_sym = 0.001        # Durée d'un symbole (1 ms)
num_symbols = 1000   # Nombre de symboles
EbN0_dB = 10         # Rapport signal/bruit (dB)

# Calculs dérivés
samples_per_symbol = int(fs * t_sym)
t = np.linspace(0, t_sym, samples_per_symbol, endpoint=False)

# Porteuses orthogonales (normalisées)
carrier_I = np.sqrt(2) * np.cos(2 * np.pi * fc * t)  # In-Phase (cos)
carrier_Q = np.sqrt(2) * np.sin(2 * np.pi * fc * t)  # Quadrature (sin)

# Génération des phases aléatoires (symboles M-PSK)
phases = 2 * np.pi * np.random.randint(0, M, num_symbols) / M
I = np.cos(phases)  # Composante I
Q = np.sin(phases)  # Composante Q

# Modulation
modulated_signal = np.concatenate([
    I[i] * carrier_I - Q[i] * carrier_Q  # Signal modulé : I·cos - Q·sin
    for i in range(num_symbols)
])

# Ajout de bruit
Eb = np.mean(modulated_signal**2) * t_sym / np.log2(M)
N0 = Eb / (10 ** (EbN0_dB / 10))
noise = np.sqrt(N0 * fs) * np.random.randn(len(modulated_signal))
received_signal = modulated_signal + noise

# Constellation avant corrélation (échantillons temporels)
received_samples_I = received_signal * np.tile(carrier_I, num_symbols)
received_samples_Q = -received_signal * np.tile(carrier_Q, num_symbols)

# Démodulation par corrélation (après intégration)
received_I = []
received_Q = []
for i in range(num_symbols):
    segment = received_signal[i*samples_per_symbol:(i+1)*samples_per_symbol]
    I_hat = np.mean(segment * carrier_I)  # Intégration sur la période
    Q_hat = -np.mean(segment * carrier_Q) # Intégration sur la période
    received_I.append(I_hat)
    received_Q.append(Q_hat)

# Tracé des constellations
plt.figure(figsize=(15, 5))

# Constellation AVANT corrélation (bruit + porteuse)
plt.subplot(1, 3, 1)
plt.scatter(received_samples_I[::10], received_samples_Q[::10], 
            c='green', alpha=0.1, s=10, label="Échantillons bruts")
plt.scatter(I, Q, c='blue', marker='x', label="Symboles émis")
plt.title("Constellation avant corrélation\n(Signal bruité + porteuse)")
plt.xlabel("I")
plt.ylabel("Q")
plt.grid(True)
plt.axis('equal')
plt.legend()

# Constellation APRÈS corrélation (intégration)
plt.subplot(1, 3, 2)
plt.scatter(received_I, received_Q, c='red', alpha=0.5, label="Reçus")
plt.scatter(I, Q, c='blue', marker='x', label="Émis")
plt.title("Constellation après corrélation\n(Orthogonalité I/Q restaurée)")
plt.xlabel("I")
plt.ylabel("Q")
plt.grid(True)
plt.axis('equal')
plt.legend()

# Zoom sur un symbole (Avant vs Après)
plt.subplot(1, 3, 3)
symbol_idx = 0  # Premier symbole
segment_I = received_samples_I[symbol_idx*samples_per_symbol:(symbol_idx+1)*samples_per_symbol]
segment_Q = received_samples_Q[symbol_idx*samples_per_symbol:(symbol_idx+1)*samples_per_symbol]
plt.scatter(segment_I, segment_Q, c='green', alpha=0.8, s=10, label="Avant corrélation")
plt.scatter(received_I[symbol_idx], received_Q[symbol_idx], c='red', label="Après corrélation")
plt.scatter(I[symbol_idx], Q[symbol_idx], c='blue', marker='x', label="Émis")
plt.title(f"Zoom sur le symbole {symbol_idx}")
plt.xlabel("I")
plt.ylabel("Q")
plt.grid(True)
plt.axis('equal')
plt.legend()

plt.tight_layout()
plt.show()