""" 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)