""" Service de calculs de cycles frigorifiques. Ce module fournit les fonctionnalités pour calculer les performances d'un cycle frigorifique: - Cycle simple (compression simple) - Cycle avec économiseur (double étage) - Calculs de COP, puissance, rendement """ import math from typing import Optional, Tuple, List, Dict, Any from dataclasses import dataclass from app.core.refrigerant_loader import RefrigerantLibrary @dataclass class ThermodynamicState: \"\"\"État thermodynamique complet d'un point.\"\"\" pressure: float # bar temperature: float # °C enthalpy: float # kJ/kg entropy: float # kJ/kg.K density: Optional[float] = None # kg/m³ quality: Optional[float] = None # 0-1 class CycleCalculator: \"\"\"Calculateur de cycles frigorifiques.\"\"\" def __init__(self, refrigerant: RefrigerantLibrary): \"\"\" Initialise le calculateur. Args: refrigerant: Instance de RefrigerantLibrary \"\"\" self.refrigerant = refrigerant def get_pressure_from_saturation_temperature(self, temperature_celsius: float, quality: float = 0.5) -> float: \"\"\" Calcule la pression de saturation à partir d'une température. Args: temperature_celsius: Température de saturation (°C) quality: Qualité pour le calcul (0.0 pour liquide, 1.0 pour vapeur, 0.5 par défaut) Returns: Pression de saturation (bar) \"\"\" temperature_kelvin = temperature_celsius + 273.15 pressure_pa = self.refrigerant.p_Tx(temperature_kelvin, quality) # p_Tx retourne des Pascals, convertir en bar pressure_bar = pressure_pa / 1e5 return pressure_bar def calculate_compressor_efficiency(self, pressure_ratio: float) -> float: \"\"\" Calcule le rendement isentropique du compresseur basé sur le rapport de pression. Utilise une corrélation empirique typique pour compresseurs frigorifiques: η_is = 0.90 - 0.04 × ln(PR) Cette formule reflète la dégradation du rendement avec l'augmentation du rapport de pression. Args: pressure_ratio: Rapport de pression P_cond / P_evap Returns: Rendement isentropique (0-1) Note: - PR = 2.0 → η ≈ 0.87 (87%) - PR = 4.0 → η ≈ 0.84 (84%) - PR = 6.0 → η ≈ 0.83 (83%) - PR = 8.0 → η ≈ 0.82 (82%) \"\"\" if pressure_ratio < 1.0: raise ValueError(f\"Le rapport de pression doit être >= 1.0, reçu: {pressure_ratio}\") # Formule empirique typique efficiency = 0.90 - 0.04 * math.log(pressure_ratio) # Limiter entre des valeurs réalistes efficiency = max(0.60, min(0.90, efficiency)) return efficiency def calculate_point_px( self, pressure: float, quality: float ) -> ThermodynamicState: \"\"\" Calcule l'état thermodynamique à partir de P et x. Args: pressure: Pression (bar) quality: Titre vapeur (0-1) Returns: État thermodynamique complet \"\"\" # RefrigerantLibrary prend bar directement T_K = self.refrigerant.T_px(pressure, quality) h_J = self.refrigerant.h_px(pressure, quality) s_J = self.refrigerant.s_px(pressure, quality) rho = self.refrigerant.rho_px(pressure, quality) return ThermodynamicState( pressure=pressure, # bar temperature=T_K - 273.15, # °C enthalpy=h_J / 1000, # kJ/kg entropy=s_J / 1000, # kJ/kg.K (CORRECTION: était J/kg.K) density=rho, # kg/m³ quality=quality ) def calculate_point_ph( self, pressure: float, enthalpy: float ) -> ThermodynamicState: \"\"\" Calcule l'état thermodynamique à partir de P et h. Args: pressure: Pression (bar) enthalpy: Enthalpie (kJ/kg) Returns: État thermodynamique complet \"\"\" # RefrigerantLibrary prend bar et J/kg h_J = enthalpy * 1000 x = self.refrigerant.x_ph(pressure, h_J) T_K = self.refrigerant.T_px(pressure, x) s_J = self.refrigerant.s_px(pressure, x) rho = self.refrigerant.rho_px(pressure, x) return ThermodynamicState( pressure=pressure, # bar temperature=T_K - 273.15, # °C enthalpy=enthalpy, # kJ/kg entropy=s_J / 1000, # kJ/kg.K (CORRECTION: était J/kg.K) density=rho, # kg/m³ quality=x if 0 <= x <= 1 else None ) def calculate_superheat_point( self, pressure: float, superheat: float ) -> ThermodynamicState: \"\"\" Calcule un point avec surchauffe. Args: pressure: Pression (bar) superheat: Surchauffe (°C) Returns: État thermodynamique \"\"\" # RefrigerantLibrary prend bar directement # Température de saturation T_sat_K = self.refrigerant.T_px(pressure, 1.0) T_K = T_sat_K + superheat # Propriétés à P et T h_J = self.refrigerant.h_pT(pressure, T_K) return self.calculate_point_ph(pressure, h_J / 1000) def calculate_subcool_point( self, pressure: float, subcool: float ) -> ThermodynamicState: \"\"\" Calcule un point avec sous-refroidissement. Args: pressure: Pression (bar) subcool: Sous-refroidissement (°C) Returns: État thermodynamique \"\"\" # RefrigerantLibrary prend bar directement # Température de saturation T_sat_K = self.refrigerant.T_px(pressure, 0.0) T_K = T_sat_K - subcool # Propriétés à P et T h_J = self.refrigerant.h_pT(pressure, T_K) return self.calculate_point_ph(pressure, h_J / 1000) def calculate_isentropic_compression( self, p_in: float, h_in: float, p_out: float ) -> ThermodynamicState: \"\"\" Calcule la compression isentropique (approximation). Args: p_in: Pression entrée (bar) h_in: Enthalpie entrée (kJ/kg) p_out: Pression sortie (bar) Returns: État en sortie (compression isentropique approximée) \"\"\" # État d'entrée state_in = self.calculate_point_ph(p_in, h_in) # Méthode simplifiée: utiliser relation polytropique # Pour un gaz réel: T_out/T_in = (P_out/P_in)^((k-1)/k) # Approximation pour réfrigérants: k ≈ 1.15 k = 1.15 T_in_K = state_in.temperature + 273.15 T_out_K = T_in_K * ((p_out / p_in) ** ((k - 1) / k)) # Calculer enthalpie à P_out et T_out h_out_J = self.refrigerant.h_pT(p_out, T_out_K) return self.calculate_point_ph(p_out, h_out_J / 1000) def calculate_simple_cycle( self, evap_pressure: float, cond_pressure: float, superheat: float = 5.0, subcool: float = 3.0, compressor_efficiency: float = 0.70, mass_flow: float = 0.1 ) -> Dict[str, Any]: \"\"\" Calcule un cycle frigorifique simple (4 points). Args: evap_pressure: Pression évaporation (bar) cond_pressure: Pression condensation (bar) superheat: Surchauffe (°C) subcool: Sous-refroidissement (°C) compressor_efficiency: Rendement isentropique compresseur mass_flow: Débit massique (kg/s) Returns: Dictionnaire avec points et performances \"\"\" # Point 1: Sortie évaporateur (aspiration compresseur) point1 = self.calculate_superheat_point(evap_pressure, superheat) # Point 2s: Refoulement isentropique point2s = self.calculate_isentropic_compression( evap_pressure, point1.enthalpy, cond_pressure ) # Point 2: Refoulement réel h2 = point1.enthalpy + (point2s.enthalpy - point1.enthalpy) / compressor_efficiency point2 = self.calculate_point_ph(cond_pressure, h2) # Point 3: Sortie condenseur point3 = self.calculate_subcool_point(cond_pressure, subcool) # Point 4: Sortie détendeur (détente isenthalpique) point4 = self.calculate_point_ph(evap_pressure, point3.enthalpy) # Calculs de performances q_evap = point1.enthalpy - point4.enthalpy # kJ/kg w_comp = point2.enthalpy - point1.enthalpy # kJ/kg q_cond = point2.enthalpy - point3.enthalpy # kJ/kg cooling_capacity = mass_flow * q_evap # kW compressor_power = mass_flow * w_comp # kW heating_capacity = mass_flow * q_cond # kW cop = q_evap / w_comp if w_comp > 0 else 0 compression_ratio = cond_pressure / evap_pressure # Débit volumique à l'aspiration volumetric_flow = None if point1.density and point1.density > 0: volumetric_flow = (mass_flow / point1.density) * 3600 # m³/h return { \"points\": [ { \"point_id\": \"1\", \"description\": \"Evaporator Outlet (Suction)\", \"pressure\": point1.pressure, \"temperature\": point1.temperature, \"enthalpy\": point1.enthalpy, \"entropy\": point1.entropy, \"quality\": point1.quality }, { \"point_id\": \"2\", \"description\": \"Compressor Discharge\", \"pressure\": point2.pressure, \"temperature\": point2.temperature, \"enthalpy\": point2.enthalpy, \"entropy\": point2.entropy, \"quality\": point2.quality }, { \"point_id\": \"3\", \"description\": \"Condenser Outlet\", \"pressure\": point3.pressure, \"temperature\": point3.temperature, \"enthalpy\": point3.enthalpy, \"entropy\": point3.entropy, \"quality\": point3.quality }, { \"point_id\": \"4\", \"description\": \"Expansion Valve Outlet\", \"pressure\": point4.pressure, \"temperature\": point4.temperature, \"enthalpy\": point4.enthalpy, \"entropy\": point4.entropy, \"quality\": point4.quality } ], \"performance\": { \"cop\": cop, \"cooling_capacity\": cooling_capacity, \"heating_capacity\": heating_capacity, \"compressor_power\": compressor_power, \"compressor_efficiency\": compressor_efficiency, \"mass_flow\": mass_flow, \"volumetric_flow\": volumetric_flow, \"compression_ratio\": compression_ratio, \"discharge_temperature\": point2.temperature }, \"diagram_data\": { \"cycle_points\": [ {\"enthalpy\": point1.enthalpy, \"pressure\": point1.pressure}, {\"enthalpy\": point2.enthalpy, \"pressure\": point2.pressure}, {\"enthalpy\": point3.enthalpy, \"pressure\": point3.pressure}, {\"enthalpy\": point4.enthalpy, \"pressure\": point4.pressure}, {\"enthalpy\": point1.enthalpy, \"pressure\": point1.pressure} # Fermer le cycle ] } }