diagram_ph/app/services/cycle_calculator_clean.py

355 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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
]
}
}