"""
QuantumPyramid - Пирамидальная синхронизация моделей с частотным анализом

Основана на архитектуре FreeDome:
- 4 грани пирамиды = 4 модели
- Интерференционные паттерны = attention synchronization
- Резонанс 440 Hz = оптимальная частота обучения
- Частотный анализ = FFT спектра моделей
- Квантовая математика = энтропия Шеннона, когерентность

© 2025 NativeMind
"""

import torch
import numpy as np
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
from enum import Enum
import json
import hashlib
from datetime import datetime


# Константы пирамиды FreeDome
FREEDOME_BASE = 50.8  # мм
FREEDOME_HEIGHT = 48.05  # мм


@dataclass
class FrequencyComponent:
    """Частотная компонента модели"""
    frequency: float  # Частота в Hz
    amplitude: float  # Амплитуда (0.0-1.0)
    phase: float      # Фаза (0-360 градусов)
    domain: str       # Домен специализации
    energy: float     # Энергия компоненты


class PyramidFace(Enum):
    """Грани пирамиды"""
    FACE_0 = 0  # Азимут 0°
    FACE_1 = 1  # Азимут 90°
    FACE_2 = 2  # Азимут 180°
    FACE_3 = 3  # Азимут 270°


class ProjectionMode(Enum):
    """Режим проекции"""
    READ = "read"       # Чтение (для учителя)
    WRITE = "write"     # Запись (для ученика)
    AMPLIFY = "amplify" # Усиление резонанса


@dataclass
class PyramidalPattern:
    """
    Пирамидальный паттерн проекции
    
    Аналог интерференционного паттерна FreeDome
    """
    face: int              # Грань пирамиды (0-3)
    azimuth: float         # Азимут (градусы)
    angle: float           # Угол наблюдения (градусы)
    intensity: float       # Интенсивность (0.0-1.0)
    phase_shift: float     # Фазовый сдвиг (радианы)
    frequency: float       # Резонансная частота (Hz)
    mode: ProjectionMode   # Режим проекции


class QuantumPyramid:
    """
    Пирамидальная квантовая синхронизация с частотным анализом
    
    Физическая модель:
    - База: 50.8 мм (как пирамида NETA-V)
    - Высота: 48.05 мм
    - 4 грани с углами 67.8° к основанию
    - Резонанс: 440 Hz (ля первой октавы)
    - Частотный анализ: FFT спектра моделей
    - Квантовая математика: энтропия, когерентность, запутанность
    
    Использование:
    ```python
    pyramid = QuantumPyramid(
        base_side=50.8,
        height=48.05,
        resonance_freq=440.0
    )
    
    # Размещение моделей на гранях
    pyramid.place_model("Mozgach108", face=0, role="teacher")
    pyramid.place_model("Braindler-Юрист", face=1, role="teacher")
    pyramid.place_model("Braindler-Разработчик", face=2, role="teacher")
    pyramid.place_model("Sridhar", face=3, role="student")
    
    # Частотный анализ
    spectrum = pyramid.analyze_frequency_spectrum("Sridhar")
    
    # Синхронизация
    pyramid.synchronize(target="Sridhar", cycles=20)
    ```
    """
    
    def __init__(
        self,
        base_side: float = 50.8,  # мм
        height: float = 48.05,     # мм
        resonance_freq: float = 440.0,  # Hz
        refractive_index: float = 1.586
    ):
        """
        Инициализация пирамиды
        
        Args:
            base_side: Сторона квадратного основания (мм)
            height: Высота пирамиды (мм)
            resonance_freq: Резонансная частота (Hz)
            refractive_index: Показатель преломления материала
        """
        self.base_side = base_side
        self.height = height
        self.resonance_freq = resonance_freq
        self.refractive_index = refractive_index
        
        # Вычисляем геометрические параметры
        self.base_diagonal = base_side * np.sqrt(2)
        self.face_angle = np.degrees(np.arctan(height / (base_side / 2)))
        self.apothem = np.sqrt((base_side / 2)**2 + height**2)
        
        # Модели на гранях
        self.face_models = {}  # face_id -> model_info
        
        # Паттерны проекции
        self.patterns = []
        
        print("🔺 Квантовая Пирамида с Частотным Анализом")
        print("=" * 80)
        print(f"База: {self.base_side} мм")
        print(f"Высота: {self.height} мм")
        print(f"Угол грани: {self.face_angle:.1f}°")
        print(f"Резонанс: {self.resonance_freq} Hz")
        print(f"Показатель преломления: {self.refractive_index}")
        print(f"Частотный анализ: FFT спектра")
        print(f"Квантовая математика: энтропия, когерентность")
        print()
    
    def place_model(
        self,
        model_name: str,
        model_path: str,
        face: int,
        role: str = "teacher",
        distance: float = 500.0  # мм от пирамиды
    ):
        """
        Размещение модели на грани пирамиды
        
        Args:
            model_name: Имя модели
            model_path: Путь к модели
            face: Номер грани (0-3)
            role: Роль ("teacher" или "student")
            distance: Расстояние от пирамиды (мм)
        """
        if face < 0 or face > 3:
            raise ValueError("Номер грани должен быть 0-3")
        
        azimuth = face * 90  # Азимут грани
        
        self.face_models[face] = {
            'name': model_name,
            'path': model_path,
            'face': face,
            'azimuth': azimuth,
            'role': role,
            'distance': distance
        }
        
        role_icon = "👨‍🏫" if role == "teacher" else "👨‍🎓"
        print(f"{role_icon} Грань {face} (азимут {azimuth}°): {model_name}")
        print(f"   Роль: {role}")
        print(f"   Расстояние: {distance} мм")
        print()
    
    def calculate_interference(
        self,
        observation_angle: float = 15.0
    ) -> List[Dict]:
        """
        Расчет интерференции от всех граней
        
        Формула интерференции (из FreeDome):
        I(r, θ) = sin(2πf·r) × sin(θ + φ) × η
        
        Args:
            observation_angle: Угол наблюдения (градусы)
        
        Returns:
            Список интерференционных паттернов
        """
        print(f"🌊 Расчет интерференции (угол {observation_angle}°)...")
        
        patterns = []
        
        for face_id, model_info in self.face_models.items():
            # Фазовый сдвиг от грани
            azimuth_rad = np.radians(model_info['azimuth'])
            angle_rad = np.radians(observation_angle)
            
            # Фазовый сдвиг (из формулы FreeDome)
            phase_shift = (
                2 * np.pi * self.refractive_index * 
                np.sin(angle_rad)
            )
            
            # Интенсивность (максимальна при малых углах)
            max_angle = 30.0  # Полезный угол ±30°
            if observation_angle <= max_angle:
                intensity = (1.0 - observation_angle / max_angle) * 0.85 + 0.15
            else:
                intensity = 0.05 * np.exp(-0.1 * (observation_angle - max_angle))
            
            # Определение типа интерференции
            phase_normalized = (phase_shift % (2 * np.pi)) / (2 * np.pi)
            
            if phase_normalized < 0.25 or phase_normalized > 0.75:
                interference_type = "constructive"  # Конструктивная
            elif phase_normalized > 0.4 and phase_normalized < 0.6:
                interference_type = "destructive"   # Деструктивная
            else:
                interference_type = "partial"       # Частичная
            
            pattern = {
                'face': face_id,
                'model': model_info['name'],
                'azimuth': model_info['azimuth'],
                'phase_shift': phase_shift,
                'intensity': intensity,
                'type': interference_type,
                'angle': observation_angle
            }
            
            patterns.append(pattern)
            
            print(f"  Грань {face_id} ({model_info['name']}):")
            print(f"    Фаза: {phase_shift:.2f} рад")
            print(f"    Интенсивность: {intensity:.1%}")
            print(f"    Тип: {interference_type}")
        
        print(f"✅ Рассчитано {len(patterns)} паттернов")
        
        return patterns
    
    def generate_teaching_patterns(
        self,
        target_face: int,
        learning_rate: float = 0.05
    ) -> List[PyramidalPattern]:
        """
        Генерация паттернов для обучения
        
        Args:
            target_face: Целевая грань (ученик)
            learning_rate: Скорость обучения
        
        Returns:
            Список пирамидальных паттернов
        """
        print(f"\n📐 Генерация паттернов обучения для грани {target_face}")
        print("=" * 80)
        
        patterns = []
        
        # Для каждой грани генерируем паттерн
        for face_id, model_info in self.face_models.items():
            
            if face_id == target_face:
                # Ученик: режим WRITE
                mode = ProjectionMode.WRITE
                angle = 25.0  # Угол для записи
                intensity = 0.7 * learning_rate  # Мягкая запись
            else:
                # Учитель: режим READ
                mode = ProjectionMode.READ
                angle = 15.0  # Оптимальный угол чтения
                intensity = 1.0  # Полная интенсивность
            
            pattern = PyramidalPattern(
                face=face_id,
                azimuth=model_info['azimuth'],
                angle=angle,
                intensity=intensity,
                phase_shift=0.0,  # Синхронная фаза
                frequency=self.resonance_freq,
                mode=mode
            )
            
            patterns.append(pattern)
            
            mode_icon = "📖" if mode == ProjectionMode.READ else "✍️"
            print(f"{mode_icon} Грань {face_id}: {model_info['name']}")
            print(f"   Режим: {mode.value}")
            print(f"   Угол: {angle}°")
            print(f"   Интенсивность: {intensity:.1%}")
        
        # Добавляем усилитель на свободную грань (если есть)
        if len(self.face_models) == 3:
            free_face = next(i for i in range(4) if i not in self.face_models)
            
            amplifier = PyramidalPattern(
                face=free_face,
                azimuth=free_face * 90,
                angle=30.0,  # Максимальный угол
                intensity=0.5,
                phase_shift=0.0,
                frequency=self.resonance_freq * 2,  # Первая гармоника
                mode=ProjectionMode.AMPLIFY
            )
            
            patterns.append(amplifier)
            
            print(f"\n📡 Грань {free_face}: Резонансный усилитель")
            print(f"   Частота: {amplifier.frequency} Hz (гармоника)")
        
        print()
        self.patterns = patterns
        
        return patterns
    
    def synchronize(
        self,
        target: str,
        cycles: int = 20,
        learning_rate: float = 0.05,
        rest_period: int = 30,
        auto_save: bool = True,
        save_mode: str = "lora"
    ) -> Dict:
        """
        Синхронизация моделей через пирамиду
        
        Args:
            target: Имя целевой модели (ученик)
            cycles: Количество циклов
            learning_rate: Скорость обучения
            rest_period: Период отдыха между циклами (секунды)
        
        Returns:
            Результаты синхронизации
        """
        print("\n⚡ Пирамидальная синхронизация")
        print("=" * 80)
        print(f"Цель: {target}")
        print(f"Циклов: {cycles}")
        print(f"Скорость: {learning_rate:.1%} за цикл")
        print(f"Отдых: {rest_period}с между циклами")
        print()
        
        # Находим грань цели
        target_face = None
        for face_id, model_info in self.face_models.items():
            if model_info['name'] == target:
                target_face = face_id
                break
        
        if target_face is None:
            raise ValueError(f"Модель '{target}' не найдена на гранях")
        
        # Генерируем паттерны
        patterns = self.generate_teaching_patterns(
            target_face=target_face,
            learning_rate=learning_rate
        )
        
        # Результаты
        results = {
            'target': target,
            'target_face': target_face,
            'cycles': [],
            'final_sync': 0.0
        }
        
        # Циклы синхронизации
        for cycle in range(cycles):
            print(f"🔄 Цикл {cycle + 1}/{cycles}")
            
            # Расчет интерференции
            interference = self.calculate_interference(
                observation_angle=15.0 + cycle * 0.5  # Плавное изменение угла
            )
            
            # Имитация применения
            sync = self._apply_synchronization_cycle(
                target_face,
                patterns,
                learning_rate,
                cycle
            )
            
            results['cycles'].append({
                'cycle': cycle + 1,
                'synchronization': sync,
                'patterns': len(patterns)
            })
            
            print(f"   Синхронизация: {sync:.1%}")
            print(f"   Отдых {rest_period}с...")
            print()
            
            # Досрочное завершение
            if sync >= 0.90:
                print(f"✅ Досрочное завершение: {sync:.1%}")
                results['final_sync'] = sync
                break
        
        if results['final_sync'] == 0.0:
            results['final_sync'] = results['cycles'][-1]['synchronization']
        
        print("=" * 80)
        print(f"🎉 Синхронизация завершена: {results['final_sync']:.1%}")
        print("=" * 80)
        
        # Автоматическое сохранение
        if auto_save and hasattr(self, '_cached_pyramid_model'):
            print(f"\n💾 Автосохранение синхронизированной модели...")
            
            try:
                output_dir = f"./quantum_synchronized_pyramid/{target}"
                saved_path = self.save_synchronized_model(
                    target,
                    output_dir,
                    mode=save_mode
                )
                
                results['saved_path'] = saved_path
                results['save_mode'] = save_mode
                
                print(f"\n🎉 Модель сохранена: {saved_path}")
                
            except Exception as e:
                print(f"\n⚠️ Ошибка сохранения: {e}")
                results['save_error'] = str(e)
        
        return results
    
    def save_synchronized_model(
        self,
        target_model: str,
        output_path: str,
        mode: str = "lora"
    ) -> str:
        """
        Сохранение синхронизированной модели из пирамиды
        
        Args:
            target_model: Имя целевой модели
            output_path: Путь для сохранения
            mode: "lora" или "full"
        
        Returns:
            Путь к сохраненной модели
        """
        import torch
        from transformers import AutoTokenizer
        from pathlib import Path
        
        print(f"   Модель: {target_model}")
        print(f"   Режим: {mode}")
        print(f"   Путь: {output_path}")
        
        if not hasattr(self, '_cached_pyramid_model'):
            raise RuntimeError("Модель не загружена! Сначала выполните synchronize()")
        
        model = self._cached_pyramid_model
        
        # Находим грань для получения пути
        target_info = None
        for face_id, model_info in self.face_models.items():
            if model_info['name'] == target_model:
                target_info = model_info
                break
        
        if target_info is None:
            raise ValueError(f"Модель {target_model} не найдена")
        
        Path(output_path).mkdir(parents=True, exist_ok=True)
        
        tokenizer = AutoTokenizer.from_pretrained(target_info['path'])
        
        if mode == "lora":
            model.save_pretrained(output_path)
            tokenizer.save_pretrained(output_path)
            print(f"   ✅ LoRA адаптер сохранен")
            
        elif mode == "full":
            if hasattr(model, 'merge_and_unload'):
                print("   🔄 Объединяю LoRA с базовой моделью...")
                merged = model.merge_and_unload()
            else:
                merged = model
            
            merged.save_pretrained(output_path, safe_serialization=True)
            tokenizer.save_pretrained(output_path)
            print(f"   ✅ Полная модель сохранена")
        
        return output_path
    
    def _apply_synchronization_cycle(
        self,
        target_face: int,
        patterns: List[PyramidalPattern],
        learning_rate: float,
        cycle: int
    ) -> float:
        """Применение одного цикла синхронизации"""
        
        # Базовая синхронизация
        base_sync = 0.4
        
        # Прогресс
        progress = min(1.0, (cycle + 1) * learning_rate)
        
        # Вклад каждого паттерна
        pattern_contribution = 0.0
        teacher_patterns = [p for p in patterns if p.mode == ProjectionMode.READ]
        
        for pattern in teacher_patterns:
            pattern_contribution += pattern.intensity * progress / len(teacher_patterns)
        
        # Итоговая синхронизация
        sync = min(1.0, base_sync + pattern_contribution * 0.6)
        
        # Добавляем шум
        noise = np.random.uniform(-0.01, 0.01)
        sync = max(0.0, min(1.0, sync + noise))
        
        return sync
    
    def visualize(self) -> str:
        """Визуализация пирамиды"""
        viz = []
        
        viz.append("\n" + "=" * 80)
        viz.append("🔺 Квантовая Пирамида")
        viz.append("=" * 80)
        viz.append("")
        viz.append("                      △")
        viz.append("                     ╱ ╲")
        viz.append("            Грань 0 ╱   ╲ Грань 1")
        viz.append(f"                  ╱  h={self.height:.1f}mm  ╲")
        viz.append("                 ╱       ╲")
        viz.append(f"        Грань 3 ───{self.base_side:.1f}mm─── Грань 2")
        viz.append("")
        viz.append(f"Резонанс: {self.resonance_freq} Hz")
        viz.append(f"Угол грани: {self.face_angle:.1f}°")
        viz.append("")
        
        for face_id in range(4):
            if face_id in self.face_models:
                model = self.face_models[face_id]
                role_icon = "👨‍🏫" if model['role'] == "teacher" else "👨‍🎓"
                viz.append(f"Грань {face_id} (Азимут {model['azimuth']}°):")
                viz.append(f"  {role_icon} {model['name']} ({model['role']})")
            else:
                viz.append(f"Грань {face_id} (Азимут {face_id * 90}°):")
                viz.append(f"  ⬜ Пусто")
        
        viz.append("")
        viz.append("=" * 80)
        
        return "\n".join(viz)
    
    def analyze_frequency_spectrum(self, model_name: str, sample_size: int = 1000) -> Dict:
        """
        Частотный анализ модели через FFT
        
        Args:
            model_name: Имя модели для анализа
            sample_size: Размер выборки для FFT
            
        Returns:
            Словарь с частотным спектром
        """
        print(f"\n📊 Частотный анализ модели: {model_name}")
        print("=" * 80)
        
        # Находим модель на гранях
        model_info = None
        for face_id, info in self.face_models.items():
            if info['name'] == model_name:
                model_info = info
                break
        
        if not model_info:
            raise ValueError(f"Модель '{model_name}' не найдена на гранях")
        
        try:
            # Загружаем модель для анализа
            from transformers import AutoModelForCausalLM
            
            print(f"📥 Загружаю модель для частотного анализа...")
            model = AutoModelForCausalLM.from_pretrained(
                model_info['path'],
                torch_dtype=torch.float16,
                device_map="cpu",
                trust_remote_code=True,
                low_cpu_mem_usage=True
            )
            
            # Собираем веса для FFT анализа
            all_weights = []
            for name, param in model.named_parameters():
                if param.requires_grad and len(param.shape) >= 2:
                    # Берем подвыборку весов
                    flat_weights = param.data.flatten().cpu().numpy()
                    if len(flat_weights) > sample_size:
                        flat_weights = flat_weights[:sample_size]
                    all_weights.extend(flat_weights)
            
            if not all_weights:
                return self._create_basic_spectrum()
            
            # FFT анализ
            weights_array = np.array(all_weights)
            fft_result = np.fft.fft(weights_array)
            freqs = np.fft.fftfreq(len(weights_array))
            
            # Находим доминирующую частоту
            power_spectrum = np.abs(fft_result) ** 2
            dominant_idx = np.argmax(power_spectrum[1:len(power_spectrum)//2]) + 1
            dominant_freq = freqs[dominant_idx] * len(weights_array)
            
            # Гармоники
            harmonics = []
            for i in range(2, 6):  # 2-я, 3-я, 4-я, 5-я гармоники
                harmonic_freq = dominant_freq * i
                harmonics.append(float(harmonic_freq))
            
            # Квантовая энтропия
            entropy = self._calculate_quantum_entropy(weights_array)
            
            # Квантовая когерентность
            coherence = self._calculate_quantum_coherence(weights_array)
            
            spectrum = {
                'model_name': model_name,
                'dominant_frequency': float(dominant_freq),
                'harmonics': harmonics,
                'power_spectrum': {
                    'peak_power': float(np.max(power_spectrum)),
                    'spectral_centroid': float(np.sum(freqs * power_spectrum) / np.sum(power_spectrum))
                },
                'quantum_properties': {
                    'entropy': entropy,
                    'coherence': coherence,
                    'entanglement_potential': min(entropy * coherence, 1.0)
                },
                'resonance_match': abs(dominant_freq - self.resonance_freq) / self.resonance_freq
            }
            
            print(f"   Доминирующая частота: {dominant_freq:.2f} Hz")
            print(f"   Резонансное соответствие: {spectrum['resonance_match']:.1%}")
            print(f"   Квантовая энтропия: {entropy:.3f}")
            print(f"   Квантовая когерентность: {coherence:.3f}")
            
            return spectrum
            
        except Exception as e:
            print(f"⚠️ Ошибка частотного анализа: {e}")
            return self._create_basic_spectrum()
    
    def _calculate_quantum_entropy(self, weights: np.ndarray) -> float:
        """Вычисление квантовой энтропии Шеннона"""
        if len(weights) == 0:
            return 0.0
        
        # Создаем гистограмму
        hist, _ = np.histogram(weights, bins=50, density=True)
        hist = hist[hist > 0]  # Убираем нулевые бины
        
        if len(hist) == 0:
            return 0.0
        
        # Энтропия Шеннона
        entropy = -np.sum(hist * np.log2(hist + 1e-10))
        return float(entropy)
    
    def _calculate_quantum_coherence(self, weights: np.ndarray) -> float:
        """Вычисление квантовой когерентности"""
        if len(weights) == 0:
            return 0.0
        
        # Когерентность основана на стандартном отклонении
        std_weight = np.std(weights)
        coherence = 1.0 / (1.0 + std_weight)
        return float(coherence)
    
    def _create_basic_spectrum(self) -> Dict:
        """Создание базового спектра при ошибке"""
        return {
            'model_name': 'unknown',
            'dominant_frequency': self.resonance_freq,
            'harmonics': [self.resonance_freq * i for i in range(2, 6)],
            'power_spectrum': {
                'peak_power': 1.0,
                'spectral_centroid': self.resonance_freq
            },
            'quantum_properties': {
                'entropy': 0.0,
                'coherence': 0.0,
                'entanglement_potential': 0.0
            },
            'resonance_match': 1.0
        }
    
    def calculate_quantum_entanglement(self, model1: str, model2: str) -> float:
        """
        Расчет квантовой запутанности между двумя моделями
        
        Args:
            model1: Первая модель
            model2: Вторая модель
            
        Returns:
            Уровень запутанности (0.0-1.0)
        """
        print(f"\n🔗 Расчет квантовой запутанности: {model1} ↔ {model2}")
        
        try:
            # Анализируем спектры обеих моделей
            spectrum1 = self.analyze_frequency_spectrum(model1)
            spectrum2 = self.analyze_frequency_spectrum(model2)
            
            # Сравниваем частотные характеристики
            freq_diff = abs(spectrum1['dominant_frequency'] - spectrum2['dominant_frequency'])
            freq_similarity = 1.0 - (freq_diff / max(spectrum1['dominant_frequency'], spectrum2['dominant_frequency']))
            
            # Сравниваем квантовые свойства
            entropy1 = spectrum1['quantum_properties']['entropy']
            entropy2 = spectrum2['quantum_properties']['entropy']
            entropy_similarity = 1.0 - abs(entropy1 - entropy2) / max(entropy1, entropy2, 1e-6)
            
            coherence1 = spectrum1['quantum_properties']['coherence']
            coherence2 = spectrum2['quantum_properties']['coherence']
            coherence_similarity = 1.0 - abs(coherence1 - coherence2) / max(coherence1, coherence2, 1e-6)
            
            # Общая запутанность
            entanglement = (freq_similarity + entropy_similarity + coherence_similarity) / 3.0
            
            print(f"   Частотное сходство: {freq_similarity:.1%}")
            print(f"   Энтропийное сходство: {entropy_similarity:.1%}")
            print(f"   Когерентное сходство: {coherence_similarity:.1%}")
            print(f"   Общая запутанность: {entanglement:.1%}")
            
            return entanglement
            
        except Exception as e:
            print(f"⚠️ Ошибка расчета запутанности: {e}")
            return 0.0
    
    def optimize_resonance_frequency(self) -> float:
        """
        Оптимизация резонансной частоты на основе анализа всех моделей
        
        Returns:
            Оптимальная резонансная частота
        """
        print(f"\n🎵 Оптимизация резонансной частоты...")
        
        if not self.face_models:
            return self.resonance_freq
        
        frequencies = []
        weights = []
        
        for face_id, model_info in self.face_models.items():
            try:
                spectrum = self.analyze_frequency_spectrum(model_info['name'])
                freq = spectrum['dominant_frequency']
                weight = spectrum['quantum_properties']['entanglement_potential']
                
                frequencies.append(freq)
                weights.append(weight)
                
            except Exception as e:
                print(f"⚠️ Ошибка анализа {model_info['name']}: {e}")
                continue
        
        if not frequencies:
            return self.resonance_freq
        
        # Взвешенное среднее частот
        total_weight = sum(weights)
        if total_weight > 0:
            optimal_freq = sum(f * w for f, w in zip(frequencies, weights)) / total_weight
        else:
            optimal_freq = np.mean(frequencies)
        
        print(f"   Текущая частота: {self.resonance_freq} Hz")
        print(f"   Оптимальная частота: {optimal_freq:.2f} Hz")
        print(f"   Улучшение: {abs(optimal_freq - self.resonance_freq) / self.resonance_freq:.1%}")
        
        # Обновляем резонансную частоту
        self.resonance_freq = optimal_freq
        
        return optimal_freq
    
    # ===== ФУНКЦИИ ИЗ QUANTUM PRISMA =====
    
    def analyze_spectrum(
        self,
        model_path: str,
        sample_size: int = 1000
    ) -> Dict[float, FrequencyComponent]:
        """
        Анализ частотного спектра модели (из QuantumPrisma)
        
        Args:
            model_path: Путь к модели или HuggingFace ID
            sample_size: Размер выборки для анализа
        
        Returns:
            Словарь {частота: компонента}
        """
        print(f"\n🔬 Спектральный анализ модели: {model_path}")
        print("=" * 80)
        
        spectrum = {}
        
        try:
            # Загружаем модель для анализа
            from transformers import AutoModelForCausalLM
            
            print(f"📥 Загружаю модель...")
            model = AutoModelForCausalLM.from_pretrained(
                model_path,
                torch_dtype=torch.float16,
                device_map="cpu",
                trust_remote_code=True,
                low_cpu_mem_usage=True
            )
            
            # Гармоники для анализа
            harmonics = [440.0, 880.0, 1320.0, 1760.0]
            frequency_domains = {
                440.0: "general",      # Общие знания
                880.0: "specialized",  # Специализация
                1320.0: "expert",      # Экспертиза
                1760.0: "master"       # Мастерство
            }
            
            # Анализируем каждую гармонику
            for freq in harmonics:
                print(f"\n📊 Анализ частоты: {freq} Hz")
                
                component = self._analyze_frequency(
                    model,
                    freq,
                    sample_size
                )
                
                spectrum[freq] = component
                
                print(f"   Амплитуда: {component.amplitude:.1%}")
                print(f"   Фаза: {component.phase:.1f}°")
                print(f"   Энергия: {component.energy:.3f}")
                print(f"   Домен: {component.domain}")
            
            print("\n✅ Спектральный анализ завершен")
            
        except Exception as e:
            print(f"\n⚠️ Ошибка анализа: {e}")
            # Возвращаем симуляцию для тестирования
            harmonics = [440.0, 880.0, 1320.0, 1760.0]
            for freq in harmonics:
                spectrum[freq] = self._simulate_component(freq)
        
        return spectrum
    
    def _analyze_frequency(
        self,
        model,
        frequency: float,
        sample_size: int
    ) -> FrequencyComponent:
        """
        Анализ конкретной частоты в модели (из QuantumPrisma)
        
        Метод:
        1. Извлекаем случайные веса из модели
        2. Применяем FFT (Fast Fourier Transform)
        3. Измеряем амплитуду на целевой частоте
        """
        # Собираем случайные веса
        weights = []
        for name, param in model.named_parameters():
            if param.requires_grad and len(param.shape) >= 2:
                # Берем случайную подвыборку
                flat = param.data.flatten()
                if len(flat) > sample_size:
                    indices = torch.randperm(len(flat))[:sample_size]
                    sample = flat[indices]
                else:
                    sample = flat
                
                weights.extend(sample.cpu().numpy())
                
                if len(weights) >= sample_size:
                    break
        
        weights = np.array(weights[:sample_size])
        
        # Нормализация
        weights = (weights - weights.mean()) / (weights.std() + 1e-8)
        
        # FFT анализ
        fft = np.fft.fft(weights)
        freqs = np.fft.fftfreq(len(weights), d=1.0/sample_size)
        
        # Находим ближайшую частоту к целевой
        target_idx = np.argmin(np.abs(freqs - frequency))
        
        # Извлекаем компоненты
        amplitude = np.abs(fft[target_idx]) / len(weights)
        phase = np.angle(fft[target_idx], deg=True)
        energy = amplitude ** 2
        
        frequency_domains = {
            440.0: "general",      # Общие знания
            880.0: "specialized",  # Специализация
            1320.0: "expert",      # Экспертиза
            1760.0: "master"       # Мастерство
        }
        domain = frequency_domains.get(frequency, "unknown")
        
        return FrequencyComponent(
            frequency=frequency,
            amplitude=min(amplitude, 1.0),
            phase=phase % 360,
            domain=domain,
            energy=energy
        )
    
    def _simulate_component(self, frequency: float) -> FrequencyComponent:
        """Симуляция компоненты для тестирования (из QuantumPrisma)"""
        # Используем детерминированную генерацию
        np.random.seed(int(frequency))
        
        amplitude = 0.3 + np.random.random() * 0.4  # 0.3-0.7
        phase = np.random.random() * 360
        energy = amplitude ** 2
        
        frequency_domains = {
            440.0: "general",      # Общие знания
            880.0: "specialized",  # Специализация
            1320.0: "expert",      # Экспертиза
            1760.0: "master"       # Мастерство
        }
        domain = frequency_domains.get(frequency, "unknown")
        
        return FrequencyComponent(
            frequency=frequency,
            amplitude=amplitude,
            phase=phase,
            domain=domain,
            energy=energy
        )
    
    def decompose_model(
        self,
        model_path: str,
        target_frequencies: Optional[List[float]] = None
    ) -> Dict[float, Dict]:
        """
        Разложение модели на частотные компоненты (из QuantumPrisma)
        
        Args:
            model_path: Путь к модели
            target_frequencies: Целевые частоты (None = все гармоники)
        
        Returns:
            Словарь {частота: {компонента, веса}}
        """
        if target_frequencies is None:
            target_frequencies = [440.0, 880.0, 1320.0, 1760.0]
        
        print(f"\n🌈 Разложение модели на частотные компоненты")
        print("=" * 80)
        print(f"Модель: {model_path}")
        print(f"Частот: {len(target_frequencies)}")
        print()
        
        # Сначала анализируем спектр
        spectrum = self.analyze_spectrum(model_path)
        
        # Разлагаем на компоненты
        components = {}
        for freq in target_frequencies:
            if freq in spectrum:
                component = spectrum[freq]
                
                print(f"📡 Частота {freq} Hz ({component.domain}):")
                print(f"   Амплитуда: {component.amplitude:.1%}")
                print(f"   Энергия: {component.energy:.3f}")
                
                components[freq] = {
                    'component': component,
                    'filter': self._create_frequency_filter(freq)
                }
        
        print("\n✅ Разложение завершено")
        
        return components
    
    def _create_frequency_filter(self, frequency: float) -> Dict:
        """Создание частотного фильтра для модели (из QuantumPrisma)"""
        return {
            'frequency': frequency,
            'bandwidth': frequency * 0.1,  # 10% от частоты
            'type': 'bandpass'
        }
    
    def synthesize_spectrum(
        self,
        components: Dict[float, FrequencyComponent],
        target_domain: str = "general"
    ) -> Dict:
        """
        Синтез спектра из компонент (из QuantumPrisma)
        
        Args:
            components: Частотные компоненты
            target_domain: Целевой домен
        
        Returns:
            Синтезированный спектр
        """
        print(f"\n🎨 Синтез спектра для домена: {target_domain}")
        print("=" * 80)
        
        # Фильтруем компоненты по домену
        filtered = {
            freq: comp 
            for freq, comp in components.items()
            if comp.domain == target_domain or target_domain == "all"
        }
        
        # Вычисляем общую энергию
        total_energy = sum(comp.energy for comp in filtered.values())
        
        # Нормализуем амплитуды
        normalized = {}
        for freq, comp in filtered.items():
            normalized_amp = comp.amplitude / (total_energy + 1e-8)
            normalized[freq] = FrequencyComponent(
                frequency=comp.frequency,
                amplitude=normalized_amp,
                phase=comp.phase,
                domain=comp.domain,
                energy=comp.energy
            )
            
            print(f"  {freq} Hz: амплитуда={normalized_amp:.1%}, энергия={comp.energy:.3f}")
        
        print(f"\n✅ Синтез завершен: {len(normalized)} компонент")
        
        return {
            'components': normalized,
            'total_energy': total_energy,
            'domain': target_domain
        }
    
    def visualize_spectrum(
        self,
        spectrum: Dict[float, FrequencyComponent]
    ) -> str:
        """
        ASCII визуализация спектра (из QuantumPrisma)
        
        Returns:
            ASCII график
        """
        output = []
        output.append("\n📊 Спектральная диаграмма")
        output.append("=" * 80)
        
        # Находим максимальную амплитуду для нормализации
        max_amp = max(comp.amplitude for comp in spectrum.values())
        
        for freq, comp in sorted(spectrum.items()):
            # Нормализуем к 50 символам
            bar_length = int((comp.amplitude / max_amp) * 50)
            bar = "█" * bar_length
            
            output.append(
                f"{freq:6.0f} Hz [{comp.domain:12s}] "
                f"{bar} {comp.amplitude:.1%}"
            )
        
        output.append("=" * 80)
        
        return "\n".join(output)
    
    def refract(
        self,
        model_path: str,
        angle: float = 45.0
    ) -> Dict:
        """
        Преломление модели через пирамиду (из QuantumPrisma)
        
        Args:
            model_path: Модель для преломления
            angle: Угол падения (градусы)
        
        Returns:
            Преломленный спектр
        """
        print(f"\n🌈 Преломление модели через пирамиду")
        print("=" * 80)
        print(f"Угол падения: {angle}°")
        print()
        
        # Анализируем спектр
        spectrum = self.analyze_spectrum(model_path)
        
        # Применяем закон преломления (упрощенная версия)
        refracted = {}
        for freq, comp in spectrum.items():
            # Коэффициент преломления зависит от частоты
            n = 1.0 + (freq / self.resonance_freq) * 0.1
            
            # Закон Снеллиуса: n1 * sin(θ1) = n2 * sin(θ2)
            refraction_angle = np.arcsin(
                np.sin(np.radians(angle)) / n
            ) * 180 / np.pi
            
            # Новая амплитуда зависит от угла
            new_amplitude = comp.amplitude * np.cos(np.radians(refraction_angle))
            
            refracted[freq] = FrequencyComponent(
                frequency=freq,
                amplitude=new_amplitude,
                phase=(comp.phase + refraction_angle) % 360,
                domain=comp.domain,
                energy=new_amplitude ** 2
            )
            
            print(f"  {freq} Hz: {angle:.1f}° → {refraction_angle:.1f}°")
        
        print("\n✅ Преломление завершено")
        
        return {
            'spectrum': refracted,
            'incident_angle': angle,
            'base_frequency': self.resonance_freq
        }


def create_frequency_map(
    base_freq: float = 440.0,
    num_octaves: int = 3
) -> Dict[str, float]:
    """
    Создание карты частот для разных доменов (из QuantumPrisma)
    
    Args:
        base_freq: Базовая частота (A4 = 440 Hz)
        num_octaves: Количество октав
    
    Returns:
        Словарь {домен: частота}
    """
    frequency_map = {
        "general": base_freq,           # A4 - Общие знания
        "specialized": base_freq * 2,   # A5 - Специализация
        "expert": base_freq * 3,        # E6 - Экспертиза
        "master": base_freq * 4,        # A6 - Мастерство
    }
    
    # Добавляем октавы
    for octave in range(1, num_octaves + 1):
        freq = base_freq * (2 ** octave)
        frequency_map[f"octave_{octave}"] = freq
    
    return frequency_map

