# ===================================================================================================
# Advanced Equation of State Comparison and Residual Properties
# Description: 
#   This interactive notebook calculates and compares the isotherms, volume roots, 
#   and residual properties (U^R, H^R, S^R) for the van der Waals, SRK, and 
#   Peng-Robinson equations of state against real fluid data from CoolProp.
# ===================================================================================================

# Install CoolProp if it is missing (e.g., on Google Colab)
try:
    import CoolProp
except ImportError:
    %pip install -q CoolProp

import numpy as np
import matplotlib.pyplot as plt
import ipywidgets as widgets
from IPython.display import display, clear_output
import CoolProp.CoolProp as CP

# Dictionary mapping friendly names to CoolProp string identifiers
fluid_dict = {
    'Methane': 'Methane',
    'Ethane': 'Ethane',
    'Propane': 'Propane',
    'n-Hexane': 'n-Hexane',
    'n-Octane': 'n-Octane',
    'Ethanol': 'Ethanol',
    'Nitrogen': 'Nitrogen',
    'Oxygen': 'Oxygen',
    'Ammonia': 'Ammonia',
    'Carbon Dioxide': 'CarbonDioxide',
    'Carbon Monoxide': 'CarbonMonoxide',
    'Argon': 'Argon',
    'Water': 'Water',
    'R-32': 'R32'
}

def compute_all_eos_and_plot(fluid_name, T_val, T_unit, P_val, P_unit):
    fluid = fluid_dict[fluid_name]
    
    # --- 1. Unit Conversions ---
    if T_unit == '°C': T = T_val + 273.15
    elif T_unit == '°F': T = (T_val - 32) * 5/9 + 273.15
    else: T = T_val
        
    if P_unit == 'atm': P = P_val * 1.01325
    elif P_unit == 'Pa': P = P_val / 1e5
    elif P_unit == 'MPa': P = P_val * 10
    elif P_unit == 'psi': P = P_val / 14.5038
    else: P = P_val

    if T <= 0 or P <= 0:
        print("Temperature and Pressure must be greater than absolute zero.")
        return

    # --- 2. Fetch Critical Properties & Real Volume ---
    Tc = CP.PropsSI('TCRIT', fluid)
    Pc = CP.PropsSI('PCRIT', fluid) / 1e5  # Convert Pa to bar
    omega = CP.PropsSI('ACENTRIC', fluid)
    
    R_Lbar = 0.0831446 # L*bar/(mol*K)
    R_J = 8.31446      # J/(mol*K)
    V_ideal = R_Lbar * T / P
    
    # Attempt to get Vapor Pressure from CoolProp (if subcritical)
    P_sat_bar = None
    if T < Tc:
        try:
            P_sat_bar = CP.PropsSI('P', 'T', T, 'Q', 0, fluid) / 1e5
        except ValueError:
            pass
            
    # Attempt to get real volume roots from CoolProp
    v_real = []
    try:
        # Check if the fluid is in the two-phase region
        phase = CP.PhaseSI('T', T, 'P', P*1e5, fluid)
        if 'twophase' in phase.lower():
            rho_l = CP.PropsSI('Dmolar', 'T', T, 'Q', 0, fluid)
            rho_v = CP.PropsSI('Dmolar', 'T', T, 'Q', 1, fluid)
            v_real = [1/rho_l * 1000, 1/rho_v * 1000] # Convert to L/mol
        else:
            rho = CP.PropsSI('Dmolar', 'T', T, 'P', P*1e5, fluid)
            v_real = [1/rho * 1000]
    except ValueError:
        pass # CoolProp failed to converge or out of bounds

    # --- 3. Setup Models ---
    eos_data = {}
    Tr = T / Tc
    
    # Model 1: van der Waals
    a_vdw = 27 * (R_Lbar * Tc)**2 / (64 * Pc)
    b_vdw = R_Lbar * Tc / (8 * Pc)
    C2_vdw = -(b_vdw + R_Lbar * T / P)
    C1_vdw = a_vdw / P
    C0_vdw = -a_vdw * b_vdw / P
    r_vdw = np.roots([1, C2_vdw, C1_vdw, C0_vdw])
    roots_vdw = np.sort(r_vdw[np.isclose(r_vdw.imag, 0)].real)
    roots_vdw = roots_vdw[roots_vdw > b_vdw]
    eos_data['vdW'] = {'roots': roots_vdw, 'a': a_vdw, 'b': b_vdw, 'da_dT': 0, 'color': 'green'}
    
    # Model 2: SRK
    ac_srk = 0.42748 * (R_Lbar**2 * Tc**2) / Pc
    b_srk = 0.08664 * R_Lbar * Tc / Pc
    m_srk = 0.480 + 1.574 * omega - 0.176 * omega**2
    alpha_srk = (1 + m_srk * (1 - np.sqrt(Tr)))**2
    a_srk = ac_srk * alpha_srk
    da_dT_srk = -ac_srk * m_srk * np.sqrt(alpha_srk / (T * Tc))
    C2_srk = -(R_Lbar * T / P)
    C1_srk = (a_srk / P) - b_srk**2 - (R_Lbar * T * b_srk / P)
    C0_srk = -(a_srk * b_srk / P)
    r_srk = np.roots([1.0, C2_srk, C1_srk, C0_srk])
    roots_srk = np.sort(r_srk[np.abs(r_srk.imag) < 1e-9].real)
    roots_srk = roots_srk[roots_srk > b_srk]
    eos_data['SRK'] = {'roots': roots_srk, 'a': a_srk, 'b': b_srk, 'da_dT': da_dT_srk, 'color': 'blue'}
    
    # Model 3: Peng-Robinson
    ac_pr = 0.45724 * (R_Lbar**2 * Tc**2) / Pc
    b_pr = 0.07780 * R_Lbar * Tc / Pc
    m_pr = 0.37464 + 1.54226 * omega - 0.26992 * omega**2
    alpha_pr = (1 + m_pr * (1 - np.sqrt(Tr)))**2
    a_pr = ac_pr * alpha_pr
    da_dT_pr = -ac_pr * m_pr * np.sqrt(alpha_pr / (T * Tc))
    C2_pr = b_pr - (R_Lbar * T / P)
    C1_pr = (a_pr / P) - 2 * b_pr * (R_Lbar * T / P) - 3 * b_pr**2
    C0_pr = b_pr**3 + (R_Lbar * T / P) * b_pr**2 - (a_pr * b_pr / P)
    r_pr = np.roots([1.0, C2_pr, C1_pr, C0_pr])
    roots_pr = np.sort(r_pr[np.abs(r_pr.imag) < 1e-9].real)
    roots_pr = roots_pr[roots_pr > b_pr]
    eos_data['PR'] = {'roots': roots_pr, 'a': a_pr, 'b': b_pr, 'da_dT': da_dT_pr, 'color': 'red'}

    # --- 4. Print Outputs ---
    print("="*85)
    print(f"SYSTEM: {fluid_name}  |  Critical State: Tc = {Tc:.2f} K, Pc = {Pc:.2f} bar, ω = {omega:.4f}")
    
    if P_sat_bar is not None:
        print(f"TARGET: T = {T:.2f} K  |  P = {P:.2f} bar  |  V_ideal = {V_ideal:.4f} L/mol  |  Psat = {P_sat_bar:.2f} bar")
    else:
        print(f"TARGET: T = {T:.2f} K  |  P = {P:.2f} bar  |  V_ideal = {V_ideal:.4f} L/mol  |  Psat = N/A (Supercritical)")
        
    if v_real:
        print(f"TRUE VOLUMES (CoolProp): {[round(v, 4) for v in v_real]} L/mol")
    print("="*85)

    for name, data in eos_data.items():
        print(f"\n[{name} EQUATION OF STATE]")
        for i, root in enumerate(data['roots']):
            # Calculate Residuals
            a, b, da_dT = data['a'], data['b'], data['da_dT']
            V_R = root - V_ideal
            
            # Unstable root skip
            if len(data['roots']) == 3 and i == 1:
                print(f"  Root {i+1}: V = {root:.4f} L/mol (Unstable Phase)")
                continue
                
            if name == 'vdW':
                U_R = (-a / root) * 100 
                S_R = R_J * np.log(P * (root - b) / (R_Lbar * T))
            elif name == 'SRK':
                log_term = np.log((root + b) / root)
                U_R = 100 * (T * da_dT - a) / b * log_term
                S_R = R_J * np.log(P * (root - b) / (R_Lbar * T)) + 100 * (da_dT / b) * log_term
            elif name == 'PR':
                log_term = np.log((root + (1 + np.sqrt(2))*b) / (root + (1 - np.sqrt(2))*b))
                U_R = 100 * (T * da_dT - a) / (2 * np.sqrt(2) * b) * log_term
                S_R = R_J * np.log(P * (root - b) / (R_Lbar * T)) + 100 * (da_dT / (2 * np.sqrt(2) * b)) * log_term
                
            H_R = U_R + 100 * P * V_R
            
            phase_label = "Stable Phase" if len(data['roots']) == 1 else ("Liquid Root" if i == 0 else "Vapor Root")
            print(f"  Root {i+1}: V = {root:.4f} L/mol ({phase_label})")
            print(f"      U^R = {U_R:>8.1f} J/mol  |  H^R = {H_R:>8.1f} J/mol  |  S^R = {S_R:>7.3f} J/(mol·K)")
            
    print("\n" + "="*85)

    # --- 5. Plotting ---
    b_max = max(b_vdw, b_srk, b_pr)
    V_max = V_ideal * 1.5
    for data in eos_data.values():
        if len(data['roots']) == 3:
            V_max = max(V_max, data['roots'][2] * 1.3)
            
    V_arr = np.linspace(b_max * 1.05, V_max, 2000)
    
    # Isotherms
    P_iso_vdw = (R_Lbar * T) / (V_arr - b_vdw) - a_vdw / (V_arr**2)
    P_iso_srk = (R_Lbar * T) / (V_arr - b_srk) - a_srk / (V_arr * (V_arr + b_srk))
    P_iso_pr  = (R_Lbar * T) / (V_arr - b_pr) - a_pr / (V_arr**2 + 2*b_pr*V_arr - b_pr**2)
    
    plt.figure(figsize=(11, 7))
    plt.plot(V_arr, P_iso_vdw, color=eos_data['vdW']['color'], lw=2, label='van der Waals Isotherm')
    plt.plot(V_arr, P_iso_srk, color=eos_data['SRK']['color'], lw=2, label='SRK Isotherm')
    plt.plot(V_arr, P_iso_pr, color=eos_data['PR']['color'], lw=2, label='Peng-Robinson Isotherm')
    
    plt.axhline(P, color='k', linestyle=':', lw=1.5, label=f'Target Pressure ({P:.1f} bar)')
    
    # Plot roots
    for name, data in eos_data.items():
        for i, root in enumerate(data['roots']):
            marker = 'x' if (len(data['roots']) == 3 and i == 1) else 'o'
            plt.plot(root, P, marker=marker, color=data['color'], markersize=8, 
                     label=f'{name} Root' if i==0 else "")
            
    # Plot True CoolProp Roots
    for i, v in enumerate(v_real):
        plt.plot(v, P, marker='*', color='k', markersize=12, label='True Vol (CoolProp)' if i==0 else "")

    plt.ylim(0, max(P * 1.5, Pc * 1.5))
    plt.xlim(0, V_max)
    plt.xlabel('Molar Volume, V (L/mol)', fontsize=12)
    plt.ylabel('Pressure, P (bar)', fontsize=12)
    plt.title(f'Equation of State Comparison for {fluid_name} at {T:.1f} K', fontsize=14, fontweight='bold')
    
    # Prevent duplicate legend labels
    handles, labels = plt.gca().get_legend_handles_labels()
    by_label = dict(zip(labels, handles))
    plt.legend(by_label.values(), by_label.keys(), loc='upper right')
    
    plt.grid(True, linestyle='--', alpha=0.6)
    plt.show()

# --- 6. Interactive UI Setup ---
style = {'description_width': 'initial'}

fluid_dropdown = widgets.Dropdown(options=list(fluid_dict.keys()), value='Methane', description='Fluid:', style=style)

T_input = widgets.FloatText(value=300, description='Temperature:', style=style, layout=widgets.Layout(width='200px'))
T_unit = widgets.Dropdown(options=['K', '°C', '°F'], value='K', layout=widgets.Layout(width='80px'))

P_input = widgets.FloatText(value=100, description='Pressure:', style=style, layout=widgets.Layout(width='200px'))
P_unit = widgets.Dropdown(options=['bar', 'atm', 'Pa', 'MPa', 'psi'], value='bar', layout=widgets.Layout(width='80px'))

T_box = widgets.HBox([T_input, T_unit])
P_box = widgets.HBox([P_input, P_unit])

ui = widgets.VBox([fluid_dropdown, T_box, P_box])
out = widgets.interactive_output(compute_all_eos_and_plot, {
    'fluid_name': fluid_dropdown,
    'T_val': T_input,
    'T_unit': T_unit,
    'P_val': P_input,
    'P_unit': P_unit
})

display(ui, out)
# Static preview: results for the default inputs above.
# The output is saved so the course website can show it.
# To try other inputs, use the controls above, or open
# this notebook in Colab (badge on the course website).
compute_all_eos_and_plot(fluid_name=fluid_dropdown.value, T_val=T_input.value, T_unit=T_unit.value,
                         P_val=P_input.value, P_unit=P_unit.value)
=====================================================================================
SYSTEM: Methane  |  Critical State: Tc = 190.56 K, Pc = 45.99 bar, ω = 0.0114
TARGET: T = 300.00 K  |  P = 100.00 bar  |  V_ideal = 0.2494 L/mol  |  Psat = N/A (Supercritical)
TRUE VOLUMES (CoolProp): [0.2134] L/mol
=====================================================================================

[vdW EQUATION OF STATE]
  Root 1: V = 0.2032 L/mol (Stable Phase)
      U^R =  -1133.4 J/mol  |  H^R =  -1595.9 J/mol  |  S^R =  -3.686 J/(mol·K)

[SRK EQUATION OF STATE]
  Root 1: V = 0.2172 L/mol (Stable Phase)
      U^R =  -1316.9 J/mol  |  H^R =  -1639.6 J/mol  |  S^R =  -4.212 J/(mol·K)

[PR EQUATION OF STATE]
  Root 1: V = 0.2080 L/mol (Stable Phase)
      U^R =  -1343.8 J/mol  |  H^R =  -1758.1 J/mol  |  S^R =  -4.241 J/(mol·K)

=====================================================================================