300 lines
10 KiB
Python
300 lines
10 KiB
Python
"""
|
|
Module de chargement des refrigerants - Utilise directement IPM_DLL/simple_refrig_api.py
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Dict, Optional, List
|
|
|
|
# If IPM_DISABLE_NATIVE=1 is set, use a lightweight MockRefifc to avoid
|
|
# loading the native shared libraries during tests or environments where
|
|
# the native binaries are not available. This is a fast, deterministic
|
|
# fallback and prevents expensive or failing native loads at import time.
|
|
_USE_MOCK = os.environ.get("IPM_DISABLE_NATIVE", "0") in ("1", "true", "True")
|
|
|
|
class MockRefifc:
|
|
"""Minimal mock of the Refifc interface used for fast tests.
|
|
|
|
It implements only the methods the app commonly calls (p_begin, p_end,
|
|
hsl_px, hsv_px, T_px, h_pT, h_px, p_Tx) with simple deterministic
|
|
formulas so tests and diagram generation can run without native libs.
|
|
"""
|
|
def __init__(self, refrig_name: str):
|
|
self.refrig_name = refrig_name
|
|
|
|
def p_begin(self):
|
|
return 1e4 # Pa
|
|
|
|
def p_end(self):
|
|
return 4e6 # Pa
|
|
|
|
def hsl_px(self, p, x):
|
|
# return a plausible enthalpy (J/kg)
|
|
return 1e5 + 0.1 * p
|
|
|
|
def hsv_px(self, p, x):
|
|
return 2e5 + 0.1 * p
|
|
|
|
def T_px(self, p, x):
|
|
# return Kelvin
|
|
return 273.15 + 20.0 + (p / 1e5) * 5.0
|
|
|
|
def h_pT(self, p, T):
|
|
return 1.5e5 + (T - 273.15) * 1000.0
|
|
|
|
def h_px(self, p, x):
|
|
return self.hsl_px(p, x) if x == 0 else self.hsv_px(p, x)
|
|
|
|
def p_Tx(self, T, x):
|
|
# inverse of T_px approximately
|
|
return ( (T - 273.15 - 20.0) / 5.0 ) * 1e5
|
|
|
|
|
|
if _USE_MOCK:
|
|
# Use the lightweight mock implementation defined above
|
|
Refifc = MockRefifc
|
|
else:
|
|
# Prefer the packaged app.ipm module. For very old/legacy setups that still
|
|
# ship a top-level `simple_refrig_api.py` in an `IPM_DLL` folder we keep a
|
|
# fallback, but only if that file actually exists. This avoids attempting a
|
|
# top-level import when the module is provided as `app.ipm.simple_refrig_api`.
|
|
try:
|
|
# Import the package module and read attributes to allow the wrapper to
|
|
# work even when `MockRefifc` is not defined in the implementation.
|
|
import importlib
|
|
_sr_pkg = importlib.import_module('app.ipm.simple_refrig_api')
|
|
Refifc = getattr(_sr_pkg, 'Refifc')
|
|
MockRefifc = getattr(_sr_pkg, 'MockRefifc', None)
|
|
except Exception as _first_exc:
|
|
# If a legacy IPM_DLL/simple_refrig_api.py file exists, import it as a
|
|
# top-level module; otherwise re-raise the original exception.
|
|
_current_dir = Path(__file__).parent.parent.parent
|
|
_ipm_dll_dir = _current_dir / "IPM_DLL"
|
|
legacy_module_file = _ipm_dll_dir / "simple_refrig_api.py"
|
|
|
|
if legacy_module_file.exists():
|
|
if str(_ipm_dll_dir) not in sys.path:
|
|
sys.path.insert(0, str(_ipm_dll_dir))
|
|
import simple_refrig_api as _sr # type: ignore
|
|
Refifc = getattr(_sr, 'Refifc')
|
|
MockRefifc = getattr(_sr, 'MockRefifc', None)
|
|
else:
|
|
# No legacy file found; re-raise the original import error so the
|
|
# caller sees the underlying cause (missing dependency, etc.).
|
|
raise _first_exc
|
|
|
|
|
|
class RefrigerantLibrary:
|
|
"""
|
|
Wrapper autour de Refifc pour compatibilite avec l'API.
|
|
Utilise directement la classe Refifc qui fonctionne dans le code original.
|
|
"""
|
|
|
|
def __init__(self, refrig_name: str, libs_dir: Optional[Path] = None):
|
|
"""
|
|
Initialise le chargement du refrigerant.
|
|
|
|
Args:
|
|
refrig_name: Nom du refrigerant (ex: "R134a", "R290")
|
|
libs_dir: Repertoire contenant les bibliotheques (optionnel, non utilise car Refifc gere ca)
|
|
"""
|
|
self.refrig_name = refrig_name
|
|
|
|
# Utiliser Refifc directement - c'est la classe qui fonctionne dans le code original
|
|
self._refifc = Refifc(refrig_name)
|
|
|
|
# Exposer toutes les methodes de Refifc avec la meme signature
|
|
|
|
def T_px(self, p: float, x: float) -> float:
|
|
"""Température à partir de pression (Pa) et qualité (retourne K).
|
|
|
|
Note: les méthodes de bas niveau attendent et retournent des unités SI
|
|
(pression en Pa, température en K, enthalpie en J/kg, entropie en J/kg.K).
|
|
"""
|
|
return self._refifc.T_px(p, x)
|
|
|
|
def h_px(self, p: float, x: float) -> float:
|
|
"""Enthalpie à partir de pression (Pa) et qualité (retourne J/kg)"""
|
|
return self._refifc.h_px(p, x)
|
|
|
|
def h_pT(self, p: float, T: float) -> float:
|
|
"""Enthalpie à partir de pression (Pa) et température (K) (retourne J/kg)"""
|
|
return self._refifc.h_pT(p, T)
|
|
|
|
def x_ph(self, p: float, h: float) -> float:
|
|
"""Qualité à partir de pression (Pa) et enthalpie (J/kg)"""
|
|
return self._refifc.x_ph(p, h)
|
|
|
|
def p_Tx(self, T: float, x: float) -> float:
|
|
"""Pression à partir de température (K) et qualité (retourne Pa)."""
|
|
return self._refifc.p_Tx(T, x)
|
|
|
|
def Ts_px(self, p: float, x: float) -> float:
|
|
"""Température de saturation à partir de pression (Pa) et qualité (retourne K)"""
|
|
return self._refifc.Ts_px(p, x)
|
|
|
|
def rho_px(self, p: float, x: float) -> float:
|
|
"""Densité à partir de pression (Pa) et qualité"""
|
|
return self._refifc.rho_px(p, x)
|
|
|
|
def s_px(self, p: float, x: float) -> float:
|
|
"""Entropie à partir de pression (Pa) et qualité (retourne J/kg.K)"""
|
|
return self._refifc.s_px(p, x)
|
|
|
|
def hsl_px(self, p: float, x: float) -> float:
|
|
"""Enthalpie liquide saturée (retourne J/kg)"""
|
|
return self._refifc.hsl_px(p, x)
|
|
|
|
def hsv_px(self, p: float, x: float) -> float:
|
|
"""Enthalpie vapeur saturée (retourne J/kg)"""
|
|
return self._refifc.hsv_px(p, x)
|
|
|
|
def rhosl_px(self, p: float, x: float) -> float:
|
|
"""Densité liquide saturée"""
|
|
return self._refifc.rhosl_px(p, x)
|
|
|
|
def rhosv_px(self, p: float, x: float) -> float:
|
|
"""Densité vapeur saturée"""
|
|
return self._refifc.rhosv_px(p, x)
|
|
|
|
def p_begin(self) -> float:
|
|
"""Pression minimale du refrigerant (Pa)"""
|
|
return self._refifc.p_begin()
|
|
|
|
def p_end(self) -> float:
|
|
"""Pression maximale du refrigerant (Pa)"""
|
|
return self._refifc.p_end()
|
|
|
|
|
|
class RefrigerantManager:
|
|
"""Gestionnaire central pour tous les refrigerants disponibles"""
|
|
|
|
# Liste des refrigerants supportes
|
|
SUPPORTED_REFRIGERANTS = [
|
|
"R12", "R22", "R32", "R134a", "R290", "R404A", "R410A",
|
|
"R452A", "R454A", "R454B", "R502", "R507A", "R513A",
|
|
"R515B", "R717", "R744", "R1233zd", "R1234ze"
|
|
]
|
|
|
|
def __init__(self, libs_dir: Optional[Path] = None):
|
|
"""
|
|
Initialise le gestionnaire
|
|
|
|
Args:
|
|
libs_dir: Repertoire contenant les bibliotheques
|
|
"""
|
|
self.libs_dir = libs_dir
|
|
self._loaded_refrigerants: Dict[str, RefrigerantLibrary] = {}
|
|
|
|
def get_available_refrigerants(self) -> List[Dict[str, str]]:
|
|
"""
|
|
Retourne la liste des refrigerants disponibles
|
|
|
|
Returns:
|
|
Liste de dictionnaires avec nom et disponibilite
|
|
"""
|
|
available = []
|
|
|
|
# Instead of attempting to load every refrigerant (which triggers
|
|
# potentially expensive native library loads), prefer a fast check by
|
|
# detecting whether the corresponding shared object exists in the
|
|
# repository's ipm lib/so directory. Loading is left for explicit
|
|
# requests (POST /{refrig}/load) or when a refrigerant is already
|
|
# present in memory.
|
|
repo_app_dir = Path(__file__).parent.parent
|
|
libs_dir = repo_app_dir / 'ipm' / 'lib' / 'so'
|
|
|
|
for refrig in self.SUPPORTED_REFRIGERANTS:
|
|
try:
|
|
lib_file = libs_dir / f"lib{refrig}.so"
|
|
exists = lib_file.exists()
|
|
available.append({
|
|
"name": refrig,
|
|
"available": bool(exists or (refrig in self._loaded_refrigerants)),
|
|
"loaded": refrig in self._loaded_refrigerants
|
|
})
|
|
except Exception as e:
|
|
available.append({
|
|
"name": refrig,
|
|
"available": False,
|
|
"error": str(e)
|
|
})
|
|
|
|
return available
|
|
|
|
def load_refrigerant(self, refrig_name: str) -> RefrigerantLibrary:
|
|
"""
|
|
Charge un refrigerant specifique
|
|
|
|
Args:
|
|
refrig_name: Nom du refrigerant
|
|
|
|
Returns:
|
|
Instance RefrigerantLibrary
|
|
|
|
Raises:
|
|
ValueError: Si le refrigerant n'est pas supporte
|
|
RuntimeError: Si le chargement echoue
|
|
"""
|
|
if refrig_name not in self.SUPPORTED_REFRIGERANTS:
|
|
raise ValueError(
|
|
f"Refrigerant non supporte: {refrig_name}. "
|
|
f"Supportes: {', '.join(self.SUPPORTED_REFRIGERANTS)}"
|
|
)
|
|
|
|
if refrig_name in self._loaded_refrigerants:
|
|
return self._loaded_refrigerants[refrig_name]
|
|
|
|
try:
|
|
lib = RefrigerantLibrary(refrig_name, self.libs_dir)
|
|
self._loaded_refrigerants[refrig_name] = lib
|
|
return lib
|
|
except Exception as e:
|
|
raise RuntimeError(
|
|
f"Erreur chargement {refrig_name}: {e}"
|
|
)
|
|
|
|
def get_refrigerant(self, refrig_name: str) -> RefrigerantLibrary:
|
|
"""
|
|
Obtient un refrigerant (le charge si necessaire)
|
|
|
|
Args:
|
|
refrig_name: Nom du refrigerant
|
|
|
|
Returns:
|
|
Instance RefrigerantLibrary
|
|
"""
|
|
if refrig_name not in self._loaded_refrigerants:
|
|
return self.load_refrigerant(refrig_name)
|
|
return self._loaded_refrigerants[refrig_name]
|
|
|
|
def unload_all(self):
|
|
"""Decharge tous les refrigerants"""
|
|
self._loaded_refrigerants.clear()
|
|
|
|
|
|
# Instance globale du gestionnaire
|
|
_manager: Optional[RefrigerantManager] = None
|
|
|
|
|
|
def get_manager() -> RefrigerantManager:
|
|
"""Obtient l'instance globale du gestionnaire"""
|
|
global _manager
|
|
if _manager is None:
|
|
_manager = RefrigerantManager()
|
|
return _manager
|
|
|
|
|
|
def get_refrigerant(name: str) -> RefrigerantLibrary:
|
|
"""
|
|
Fonction helper pour obtenir un refrigerant
|
|
|
|
Args:
|
|
name: Nom du refrigerant
|
|
|
|
Returns:
|
|
Instance RefrigerantLibrary
|
|
"""
|
|
return get_manager().get_refrigerant(name)
|