Module pychnosz.models.supcrt92_fortran

SUPCRT92 water model with Fortran backend.

This implementation uses the original H2O92 Fortran subroutine for exact compatibility with R CHNOSZ. Falls back to Python approximations if the Fortran library is not available.

Functions

def water_SUPCRT92(property: str | List[str],
T: float | numpy.ndarray = 298.15,
P: float | str | numpy.ndarray = 1.0,
**kwargs) ‑> float | numpy.ndarray | Dict[str, Any]
Expand source code
def water_SUPCRT92(property: Union[str, List[str]], 
                   T: Union[float, np.ndarray] = 298.15,
                   P: Union[float, np.ndarray, str] = 1.0,
                   **kwargs) -> Union[float, np.ndarray, Dict[str, Any]]:
    """
    Calculate water properties using SUPCRT92 model.
    
    This function provides the same interface as the original Python
    implementation but with Fortran backend support for high accuracy.
    
    Parameters
    ----------
    property : str or list of str
        Property or list of properties to calculate
    T : float or array
        Temperature in Kelvin
    P : float, array, or 'Psat'
        Pressure in bar, or 'Psat' for saturation pressure
    **kwargs
        Additional options (e.g., Psat_floor)
    
    Returns
    -------
    float, array, or dict
        Calculated properties
        
    Examples
    --------
    >>> # Basic usage
    >>> rho = water_SUPCRT92('rho', 298.15, 1.0)
    >>> print(f"Density: {rho:.3f} g/cm³")
    
    >>> # Multiple properties  
    >>> props = water_SUPCRT92(['rho', 'epsilon'], 298.15, 1.0)
    >>> print(f"ρ = {props['rho']:.3f}, ε = {props['epsilon']:.1f}")
    
    >>> # Saturation pressure
    >>> Psat = water_SUPCRT92('Psat', 373.15, 'Psat')
    >>> print(f"Psat at 100°C: {Psat:.2f} bar")
    """
    return supcrt92_water.calculate(property, T, P, **kwargs)

Calculate water properties using SUPCRT92 model.

This function provides the same interface as the original Python implementation but with Fortran backend support for high accuracy.

Parameters

property : str or list of str
Property or list of properties to calculate
T : float or array
Temperature in Kelvin
P : float, array, or 'Psat'
Pressure in bar, or 'Psat' for saturation pressure
**kwargs
Additional options (e.g., Psat_floor)

Returns

float, array, or dict
Calculated properties

Examples

>>> # Basic usage
>>> rho = water_SUPCRT92('rho', 298.15, 1.0)
>>> print(f"Density: {rho:.3f} g/cm³")
>>> # Multiple properties  
>>> props = water_SUPCRT92(['rho', 'epsilon'], 298.15, 1.0)
>>> print(f"ρ = {props['rho']:.3f}, ε = {props['epsilon']:.1f}")
>>> # Saturation pressure
>>> Psat = water_SUPCRT92('Psat', 373.15, 'Psat')
>>> print(f"Psat at 100°C: {Psat:.2f} bar")

Classes

class SUPCRT92Water
Expand source code
class SUPCRT92Water:
    """
    SUPCRT92 water model with Fortran backend.
    
    This class provides an interface to the original SUPCRT92 Fortran
    subroutines for calculating water properties. If the Fortran library
    is not available, it falls back to Python approximations.
    
    The Fortran implementation gives exact compatibility with R CHNOSZ
    and includes all 23+ thermodynamic properties.
    """
    
    def __init__(self):
        """
        Initialize SUPCRT92 water model.
        
        SUPCRT92 requires the compiled FORTRAN interface for accuracy and 
        compatibility with R CHNOSZ. No pure Python fallback is provided.
        """
        if not HAS_FORTRAN:
            raise ImportError(
                f"SUPCRT92 water model requires compiled FORTRAN interface. "
                f"Error: {_fortran_error}. "
                f"Please compile the FORTRAN subroutines using: "
                f"python compile_fortran.py. "
                f"See setup_fortran_instructions.md for details."
            )
        
        # Initialize Fortran interface (required)
        try:
            self._fortran_interface = get_h2o92_interface()
        except Exception as e:
            raise RuntimeError(f"Failed to initialize FORTRAN interface: {e}") from e
    
    def calculate(self, 
                  properties: Union[str, List[str]], 
                  T: Union[float, np.ndarray] = 298.15,
                  P: Union[float, np.ndarray, str] = 1.0,
                  **kwargs) -> Union[float, np.ndarray, Dict[str, Any]]:
        """
        Calculate water properties using SUPCRT92 model.
        
        Parameters
        ----------
        properties : str or list of str
            Property or list of properties to calculate
        T : float or array
            Temperature in Kelvin
        P : float, array, or 'Psat'
            Pressure in bar, or 'Psat' for saturation pressure
        **kwargs
            Additional options (e.g., Psat_floor)
        
        Returns
        -------
        float, array, or dict
            Calculated properties
        """
        
        # Handle input types
        if isinstance(properties, str):
            properties = [properties]
            single_prop = True
        else:
            single_prop = False
            
        # Convert inputs to arrays
        T = np.atleast_1d(np.asarray(T, dtype=float))
        
        # Always use FORTRAN backend (no fallback)
        return self._calculate_fortran(properties, T, P, single_prop, **kwargs)
    
    def _calculate_fortran(self, properties: List[str], T: np.ndarray,
                          P: Union[np.ndarray, str], single_prop: bool,
                          **kwargs) -> Union[float, np.ndarray, Dict[str, Any]]:
        """Calculate properties using Fortran backend."""

        # Handle pressure input
        if isinstance(P, str) and P == 'Psat':
            P_vals = 'Psat'
        else:
            P = np.atleast_1d(np.asarray(P, dtype=float))
            if len(P) < len(T):
                P = np.resize(P, len(T))
            elif len(T) < len(P):
                T = np.resize(T, len(P))
            P_vals = P

        # Use batched calculation for better performance
        try:
            results = self._fortran_interface.calculate_properties_batch(
                T, P_vals, properties
            )
        except Exception as e:
            warnings.warn(f"Batch Fortran calculation failed: {e}")
            # Fallback to individual calculations if batch fails
            results = {}
            for prop in properties:
                results[prop] = np.full_like(T, np.nan)

            for i in range(len(T)):
                T_i = T[i]

                # Skip invalid points
                if np.isnan(T_i):
                    continue

                if isinstance(P_vals, str):
                    P_i = P_vals
                else:
                    P_i = P_vals[i]
                    if np.isnan(P_i):
                        continue

                try:
                    # Call Fortran interface
                    props_i = self._fortran_interface.calculate_properties(
                        T_i, P_i, properties
                    )

                    # Store results
                    for prop in properties:
                        if prop in props_i:
                            results[prop][i] = props_i[prop]

                except Exception as e:
                    warnings.warn(f"Fortran calculation failed at T={T_i:.1f}K, P={P_i}: {e}")
                    continue

        # Handle Psat_floor for saturation pressure
        if 'Psat' in results and 'Psat_floor' in kwargs:
            Psat_floor = kwargs['Psat_floor']
            if Psat_floor is not None:
                results['Psat'] = np.maximum(results['Psat'], Psat_floor)
        
        # Return results
        if single_prop:
            result = results[properties[0]]
            return result[0] if len(result) == 1 else result
        else:
            # Convert single-element arrays to scalars if appropriate
            if len(T) == 1:
                for key in results:
                    if len(results[key]) == 1:
                        results[key] = results[key][0]
            return results
    
    def available_properties(self) -> List[str]:
        """
        Get list of available properties.
        
        Returns
        -------
        List[str]
            List of available property names
        """
        if self._use_fortran:
            return self._fortran_interface.available_properties()
        else:
            return self._python_backend.available_properties()
    
    @property
    def backend(self) -> str:
        """Get the active backend ('fortran' or 'python')."""
        return 'fortran' if self._use_fortran else 'python'
    
    @property
    def has_fortran(self) -> bool:
        """Check if Fortran backend is available."""
        return HAS_FORTRAN

SUPCRT92 water model with Fortran backend.

This class provides an interface to the original SUPCRT92 Fortran subroutines for calculating water properties. If the Fortran library is not available, it falls back to Python approximations.

The Fortran implementation gives exact compatibility with R CHNOSZ and includes all 23+ thermodynamic properties.

Initialize SUPCRT92 water model.

SUPCRT92 requires the compiled FORTRAN interface for accuracy and compatibility with R CHNOSZ. No pure Python fallback is provided.

Instance variables

prop backend : str
Expand source code
@property
def backend(self) -> str:
    """Get the active backend ('fortran' or 'python')."""
    return 'fortran' if self._use_fortran else 'python'

Get the active backend ('fortran' or 'python').

prop has_fortran : bool
Expand source code
@property
def has_fortran(self) -> bool:
    """Check if Fortran backend is available."""
    return HAS_FORTRAN

Check if Fortran backend is available.

Methods

def available_properties(self) ‑> List[str]
Expand source code
def available_properties(self) -> List[str]:
    """
    Get list of available properties.
    
    Returns
    -------
    List[str]
        List of available property names
    """
    if self._use_fortran:
        return self._fortran_interface.available_properties()
    else:
        return self._python_backend.available_properties()

Get list of available properties.

Returns

List[str]
List of available property names
def calculate(self,
properties: str | List[str],
T: float | numpy.ndarray = 298.15,
P: float | str | numpy.ndarray = 1.0,
**kwargs) ‑> float | numpy.ndarray | Dict[str, Any]
Expand source code
def calculate(self, 
              properties: Union[str, List[str]], 
              T: Union[float, np.ndarray] = 298.15,
              P: Union[float, np.ndarray, str] = 1.0,
              **kwargs) -> Union[float, np.ndarray, Dict[str, Any]]:
    """
    Calculate water properties using SUPCRT92 model.
    
    Parameters
    ----------
    properties : str or list of str
        Property or list of properties to calculate
    T : float or array
        Temperature in Kelvin
    P : float, array, or 'Psat'
        Pressure in bar, or 'Psat' for saturation pressure
    **kwargs
        Additional options (e.g., Psat_floor)
    
    Returns
    -------
    float, array, or dict
        Calculated properties
    """
    
    # Handle input types
    if isinstance(properties, str):
        properties = [properties]
        single_prop = True
    else:
        single_prop = False
        
    # Convert inputs to arrays
    T = np.atleast_1d(np.asarray(T, dtype=float))
    
    # Always use FORTRAN backend (no fallback)
    return self._calculate_fortran(properties, T, P, single_prop, **kwargs)

Calculate water properties using SUPCRT92 model.

Parameters

properties : str or list of str
Property or list of properties to calculate
T : float or array
Temperature in Kelvin
P : float, array, or 'Psat'
Pressure in bar, or 'Psat' for saturation pressure
**kwargs
Additional options (e.g., Psat_floor)

Returns

float, array, or dict
Calculated properties