# -*- coding: utf-8 -*-
"""
Created on Fri Sep  5 16:47:09 2025

@author: AKourgli
"""

import numpy as np
import matplotlib.pyplot as plt
from scipy.constants import speed_of_light

# Paramètres de base
freq = 2.4e9  # Fréquence en Hz
c = speed_of_light
wavelength = c / freq

print(f"Fréquence: {freq/1e6} MHz")
print(f"Longueur d'onde: {wavelength:.3f} m")

# Fonctions pour les diagrammes de rayonnement
def dipole_pattern(theta):
    """Calcul du diagramme de rayonnement d'un dipôle demi-onde"""
    beta = 2 * np.pi / wavelength
    L = wavelength / 2  # Dipôle demi-onde
    return (np.cos((beta * L / 2) * np.cos(theta)) - np.cos(beta * L / 2)) / np.sin(theta)

def monopole_pattern(theta):
    """Diagramme de rayonnement d'un monopole quart d'onde"""
    pattern = dipole_pattern(theta)
    pattern = np.where((theta > np.pi/2) & (theta < 3*np.pi/2), 0, pattern)
    return pattern

def isotropic_pattern(theta):
    """Diagramme de rayonnement d'une antenne isotrope (cercle parfait)"""
    return np.ones_like(theta)

def parabolic_pattern(theta, beam_width=20):
    """Diagramme de rayonnement d'une antenne parabolique (faisceau étroit)"""
    beam_width_rad = np.deg2rad(beam_width)
    pattern = np.exp(-((theta - np.pi/2)**2) / (2 * (beam_width_rad/4)**2))
    return pattern

def patch_pattern(theta):
    """Diagramme de rayonnement d'une antenne patch (cos²)"""
    return np.cos(theta)**2

# Fonctions pour le calcul de l'impédance et du TOS
def calculate_swr(Z_ant, Z0=50):
    """Calcul du Taux d'Ondes Stationnaires"""
    gamma = (Z_ant - Z0) / (Z_ant + Z0)
    gamma_magnitude = abs(gamma)
    
    if gamma_magnitude >= 0.999:
        return float('inf')
    
    swr = (1 + gamma_magnitude) / (1 - gamma_magnitude)
    return swr

def dipole_impedance(freq, length, radius):
    """Calcul approximatif de l'impédance d'un dipôle"""
    k = 2 * np.pi * freq / c
    a = radius
    R = 20 * (k * length)**2
    X = 120 * (np.log(length / (2 * a)) - 1) * np.tan(k * length / 2)
    return R + 1j * X

def monopole_impedance(freq, length, radius):
    """Calcul approximatif de l'impédance d'un monopole"""
    return dipole_impedance(freq, length, radius) / 2

def isotropic_impedance(freq):
    """Impédance d'une antenne isotrope (idéale)"""
    return 50 + 0j  # Toujours adaptée

def parabolic_impedance(freq, diameter):
    """Impédance d'une antenne parabolique"""
    # L'impédance dépend principalement de l'alimentation
    # Pour une parabole avec alimentation standard
    return 75 + 0j  # Valeur typique

def patch_impedance(freq, substrate_thickness):
    """Impédance d'une antenne patch"""
    # L'impédance dépend de la géométrie et du substrat
    # Pour simplifier, nous modélisons une variation autour de 50Ω
    f0 = 2.4e9
    variation = 0.1 * (freq - f0) / f0
    return 50 * (1 + variation) + 1j * 10 * variation

# Calcul pour différentes directions
theta = np.linspace(0.001, 2*np.pi, 360)

# Calcul des patterns pour chaque type d'antenne
patterns = {
    "Dipôle λ/2": dipole_pattern(theta),
    "Monopôle λ/4": monopole_pattern(theta),
    "Isotrope": isotropic_pattern(theta),
    "Parabole": parabolic_pattern(theta),
    "Patch": patch_pattern(theta)
}

# Normalisation et conversion en dB
patterns_db = {}
for name, pattern in patterns.items():
    pattern_norm = np.abs(pattern) / np.max(np.abs(pattern))
    patterns_db[name] = 10 * np.log10(pattern_norm)

# Tracé de tous les diagrammes sur un même graphique
plt.figure(figsize=(10, 10))
ax = plt.subplot(111, projection='polar')

colors = {'Dipôle λ/2': 'blue', 'Monopôle λ/4': 'green', 'Isotrope': 'black', 
          'Parabole': 'red', 'Patch': 'orange'}

for name, pattern_db in patterns_db.items():
    ax.plot(theta, pattern_db, color=colors[name], linewidth=2, label=name)

ax.set_theta_zero_location('N')
ax.set_theta_direction(-1)
ax.set_rmin(-30)
ax.set_rmax(0)
ax.set_title("Comparaison des diagrammes de rayonnement d'antennes", va='bottom', fontsize=14)
ax.legend(loc='lower right', bbox_to_anchor=(1.1, 0.1))

plt.tight_layout()
plt.show()

# Étude de l'évolution du TOS et de l'impédance en fonction de la fréquence
frequencies = np.linspace(2.0e9, 2.8e9, 100)  # De 2.0 à 2.8 GHz

# Initialisation des dictionnaires pour stocker les résultats
impedances = {
    "Dipôle λ/2": [],
    "Monopôle λ/4": [],
    "Isotrope": [],
    "Parabole": [],
    "Patch": []
}

swr_values = {
    "Dipôle λ/2": [],
    "Monopôle λ/4": [],
    "Isotrope": [],
    "Parabole": [],
    "Patch": []
}

# Calcul pour chaque fréquence
for f in frequencies:
    # Dipôle
    Z_dipole = dipole_impedance(f, wavelength/2, 0.0001)
    impedances["Dipôle λ/2"].append(Z_dipole)
    swr_values["Dipôle λ/2"].append(calculate_swr(Z_dipole))
    
    # Monopôle
    Z_monopole = monopole_impedance(f, wavelength/4, 0.0001)
    impedances["Monopôle λ/4"].append(Z_monopole)
    swr_values["Monopôle λ/4"].append(calculate_swr(Z_monopole))
    
    # Isotrope
    Z_isotropic = isotropic_impedance(f)
    impedances["Isotrope"].append(Z_isotropic)
    swr_values["Isotrope"].append(calculate_swr(Z_isotropic))
    
    # Parabole
    Z_parabolic = parabolic_impedance(f, 0.6)
    impedances["Parabole"].append(Z_parabolic)
    swr_values["Parabole"].append(calculate_swr(Z_parabolic))
    
    # Patch
    Z_patch = patch_impedance(f, 0.0016)
    impedances["Patch"].append(Z_patch)
    swr_values["Patch"].append(calculate_swr(Z_patch))

# Conversion en tableaux numpy pour faciliter le traitement
for name in impedances.keys():
    impedances[name] = np.array(impedances[name])
    swr_values[name] = np.array(swr_values[name])

# Tracé des graphiques d'évolution
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes = axes.flatten()

# Tracé pour chaque type d'antenne
for i, name in enumerate(impedances.keys()):
    ax = axes[i]
    
    # Tracé de l'impédance (partie réelle et imaginaire)
    ax.plot(frequencies/1e9, np.real(impedances[name]), 'b-', label='Partie réelle')
    ax.plot(frequencies/1e9, np.imag(impedances[name]), 'r-', label='Partie imaginaire')
    
    # Tracé du TOS sur un second axe y
    ax2 = ax.twinx()
    ax2.plot(frequencies/1e9, swr_values[name], 'g--', label='TOS')
    
    ax.set_xlabel('Fréquence (GHz)')
    ax.set_ylabel('Impédance (Ω)')
    ax2.set_ylabel('TOS')
    ax.set_title(f'{name} - Évolution avec la fréquence')
    ax.grid(True)
    
    # Ajout des légendes
    ax.legend(loc='upper left')
    ax2.legend(loc='upper right')

# Cacher le dernier subplot s'il y en a un de trop
if len(impedances) < len(axes):
    axes[-1].set_visible(False)

plt.tight_layout()
plt.show()

# Tracé des diagrammes de rayonnement individuels
fig, axes = plt.subplots(2, 3, subplot_kw={'projection': 'polar'}, figsize=(15, 10))
axes = axes.flatten()

for i, (name, pattern_db) in enumerate(patterns_db.items()):
    ax = axes[i]
    ax.plot(theta, pattern_db, color=colors[name], linewidth=2)
    ax.set_title(name)
    ax.set_theta_zero_location('N')
    ax.set_theta_direction(-1)
    ax.set_rmin(-30)
    ax.set_rmax(0)

# Cacher le dernier subplot s'il y en a un de trop
if len(patterns_db) < len(axes):
    axes[-1].set_visible(False)

plt.tight_layout()
plt.show()

# Affichage des caractéristiques à la fréquence centrale
print("\nCaractéristiques des antennes à 2.4 GHz:")
for name in impedances.keys():
    Z = impedances[name][np.argmin(np.abs(frequencies - freq))]
    swr = swr_values[name][np.argmin(np.abs(frequencies - freq))]
    print(f"{name}: Impédance = {Z:.2f} Ω, TOS = {swr:.2f}")

# Calcul des gains approximatifs
gains = {
    "Dipôle λ/2": 2.15,
    "Monopôle λ/4": 2.15 - 3,
    "Isotrope": 0,
    "Parabole": 24,
    "Patch": 6
}

print("\nGains approximatifs (dBi):")
for name, gain in gains.items():
    print(f"{name}: {gain} dBi")