# RCAIDE/Methods/Powertrain/Converters/Fuel_Cells/Common/compute_stack_properties.py
# 
# 
# Created:   Nov 2024, M. Clarke 
# Modified:  Jan 2025, M. Clarke 

# ----------------------------------------------------------------------------------------------------------------------
#  IMPORT
# ----------------------------------------------------------------------------------------------------------------------
import RCAIDE
from RCAIDE.Framework.Core import Units
from RCAIDE.Library.Methods.Powertrain.Converters.Fuel_Cells.Larminie_Model import  compute_power, compute_voltage
from RCAIDE.Library.Methods.Powertrain.Converters.Fuel_Cells.Proton_Exchange_Membrane.compute_fuel_cell_performance import  evaluate_PEM ,  evaluate_max_gross_power, set_rated_current_density 
import  scipy as  sp
import  numpy as  np
import matplotlib.pyplot as  plt
import matplotlib.cm as cm

# ----------------------------------------------------------------------------------------------------------------------
#  Compute Stack Properties
# ----------------------------------------------------------------------------------------------------------------------  
def compute_stack_properties(fuel_cell_stack):
    """
    Calculates fuel cell stack properties based on individual cell characteristics and stack configuration.
    
    Parameters
    ----------
    fuel_cell_stack : RCAIDE.Components.Powertrain.Converters.Fuel_Cell_Stack
        The fuel cell stack component for which properties are being computed
        
    Returns
    -------
    None
    
    Notes
    -----
    This function computes geometric, electrical, and performance properties of a fuel cell stack
    based on individual cell properties and stack configuration. It handles both generic fuel cell
    stacks and proton exchange membrane (PEM) fuel cell stacks with different calculation methods.
    
    For generic fuel cells, optimization is used to find the maximum power point.
    For PEM fuel cells, specialized performance models are used that account for
    operating conditions like temperature and pressure.
    
    The function calculates:
        - Physical dimensions (length, width, height) based on cell arrangement
        - Maximum power, voltage, and current
        - Fuel consumption rates
        - Mass properties
    
    **Major Assumptions**
        * Cell properties are uniform across the stack
        * Cells are arranged in a regular geometric pattern
        * For PEM cells, standard atmospheric conditions are used if not specified
        * Thermal effects within the stack are simplified
    
    **Theory**
    
    For generic fuel cells, the maximum power point is found by optimizing:
    
    .. math::
        P_{max} = \\max_{j \\in [j_{min}, j_{max}]} P(j)
    
    where j is the current density and P is the power function.
    
    For PEM fuel cells, detailed electrochemical models account for:
        - Activation losses
        - Ohmic losses
        - Concentration losses
        
    See Also
    --------
    RCAIDE.Library.Methods.Powertrain.Converters.Fuel_Cells.Larminie_Model.compute_power
    RCAIDE.Library.Methods.Powertrain.Converters.Fuel_Cells.Proton_Exchange_Membrane.compute_fuel_cell_performance
    """
     
    
    series_e           = fuel_cell_stack.electrical_configuration.series
    parallel_e         = fuel_cell_stack.electrical_configuration.parallel
    n_total            = parallel_e *series_e
    normal_count       = fuel_cell_stack.geometrtic_configuration.normal_count  
    parallel_count     = fuel_cell_stack.geometrtic_configuration.parallel_count
    stacking_rows      = fuel_cell_stack.geometrtic_configuration.stacking_rows

    if int(parallel_e*series_e) != int(normal_count*parallel_count):
        raise Exception('Number of cells in gemetric layout not equal to number of cells in electric circuit configuration ')
         
    normal_spacing     = fuel_cell_stack.geometrtic_configuration.normal_spacing   
    parallel_spacing   = fuel_cell_stack.geometrtic_configuration.parallel_spacing
    volume_factor      = fuel_cell_stack.volume_packaging_factor 
    euler_angles       = fuel_cell_stack.orientation_euler_angles
    fuel_cell_length   = fuel_cell_stack.fuel_cell.length 
    fuel_cell_width    = fuel_cell_stack.fuel_cell.width   
    fuel_cell_height   = fuel_cell_stack.fuel_cell.height    
    
    x1 =  normal_count * (fuel_cell_length +  normal_spacing) * volume_factor # distance in the module-level normal direction
    x2 =  parallel_count *  (fuel_cell_width +parallel_spacing) * volume_factor # distance in the module-level parallel direction
    x3 =  fuel_cell_height * volume_factor # distance in the module-level height direction 

    length = x1 / stacking_rows
    width  = x2
    height = x3 *stacking_rows     
    
    if  euler_angles[0] == (np.pi / 2):
        x1prime      = x2
        x2prime      = -x1
        x3prime      = x3 
    if euler_angles[1] == (np.pi / 2):
        x1primeprime = -x3prime
        x2primeprime = x2prime
        x3primeprime = x1prime
    if euler_angles[2] == (np.pi / 2):
        length       = x1primeprime
        width        = x3primeprime
        height       = -x2primeprime

    # store length, width and height
    fuel_cell_stack.length = length
    fuel_cell_stack.width  = width
    fuel_cell_stack.height = height 
    fuel_cell              = fuel_cell_stack.fuel_cell 
     
    if type(fuel_cell_stack) == RCAIDE.Library.Components.Powertrain.Converters.Generic_Fuel_Cell_Stack:
        lb                                     = 0.0001/(Units.cm**2.)    #lower bound on fuel cell current density
        ub                                     = 1.2/(Units.cm**2.)
        sign                                   = -1. # used to minimize -power 
        maximum_current_density                = sp.optimize.fminbound(compute_power, lb, ub, args=(fuel_cell, sign)) 
        P_fuel_cell                            = compute_power(maximum_current_density,fuel_cell)  
        V_fuel_cell                            = compute_voltage(fuel_cell,maximum_current_density)  # useful voltage vector 
        efficiency                             = np.divide(V_fuel_cell, fuel_cell.ideal_voltage)
        mdot_H2                                = np.divide(P_fuel_cell,np.multiply(fuel_cell.propellant.specific_energy,efficiency))
        
        # store properties
        fuel_cell.rated_current_density       = maximum_current_density
        fuel_cell.volume                       = fuel_cell.interface_area*fuel_cell.wall_thickness
        fuel_cell.mass                         = fuel_cell.volume*fuel_cell.cell_density*fuel_cell.porosity_coefficient  
        fuel_cell.density                      = fuel_cell.mass/fuel_cell.volume                      
        fuel_cell.specific_power               = fuel_cell.max_power/fuel_cell.mass  
  
        fuel_cell_stack.mass_properties.mass   = n_total*fuel_cell.mass 
        fuel_cell_stack.voltage                = V_fuel_cell  * series_e
        fuel_cell_stack.maximum_voltage        = V_fuel_cell  * series_e
        fuel_cell_stack.maximum_power          = P_fuel_cell * series_e 
        fuel_cell_stack.maximum_current        = fuel_cell_stack.maximum_power / fuel_cell_stack.maximum_voltage
        fuel_cell_stack.maximum_fuel_flow_rate = mdot_H2 * n_total
    
    elif type(fuel_cell_stack) == RCAIDE.Library.Components.Powertrain.Converters.Proton_Exchange_Membrane_Fuel_Cell: 
    
        # check if mach number and temperature are passed
        design_altitude =  0
        
        # call the atmospheric model to get the conditions at the specified altitude
        atmosphere = RCAIDE.Framework.Analyses.Atmospheric.US_Standard_1976()
        atmo_data  = atmosphere.compute_values(design_altitude) 
         
        segment                                     = RCAIDE.Framework.Mission.Segments.Segment()
        segment.hybrid_power_split_ratio            = 1.0
        segment.battery_fuel_cell_power_split_ratio = 0.1
        segment.state.conditions                    = RCAIDE.Framework.Mission.Common.Results()   
        bus                                         = RCAIDE.Library.Components.Powertrain.Distributors.Electrical_Bus() 
        bus.fuel_cell_stacks.append(fuel_cell_stack)   
        
        bus.append_operating_conditions(segment)
        for fuel_cell_stack in  bus.fuel_cell_stacks: 
            fuel_cell_stack.append_operating_conditions(segment,bus)      
            
        for tag, bus_item in  bus.items():  
            if issubclass(type(bus_item), RCAIDE.Library.Components.Component):
                bus_item.append_operating_conditions(segment,bus)
 
        for cryogenic_tank in  bus.cryogenic_tanks: 
            cryogenic_tank.append_operating_conditions(segment,bus)
      
        # compute fuel cell performance             
        t_idx                                                                    =  0
        fuel_cell_stack_conditions                                               = segment.state.conditions.energy[bus.tag].fuel_cell_stacks[fuel_cell_stack.tag]
        fuel_cell_stack_conditions.fuel_cell.stagnation_temperature[t_idx, 0]    = atmo_data.temperature   
        fuel_cell_stack_conditions.fuel_cell.stagnation_pressure[t_idx, 0]       = atmo_data.pressure   
        fuel_cell_stack_conditions.fuel_cell.pressure_drop[t_idx, 0]             = fuel_cell.rated_p_drop_fc
        fuel_cell_stack_conditions.fuel_cell.stack_temperature[t_idx, 0]         = fuel_cell.stack_temperature 
        rated_current_density, rated_power_density                               = evaluate_max_gross_power(fuel_cell_stack,fuel_cell_stack_conditions,t_idx)
        set_rated_current_density(fuel_cell_stack, rated_current_density, rated_power_density)
        
        fuel_cell_stack_conditions.fuel_cell.current_density[t_idx] = rated_current_density
        m_dot_H2, V_fuel_cell, P_fuel_cell, _, _, _, _, _,_  =  evaluate_PEM(fuel_cell_stack,fuel_cell_stack_conditions, t_idx)
   
        # store properties
        area_square_meters                     = fuel_cell.interface_area * 0.0001
        fuel_cell.volume                       = area_square_meters*fuel_cell.wall_thickness
        fuel_cell.mass                         = fuel_cell.volume*fuel_cell.cell_density*fuel_cell.porosity_coefficient  
        fuel_cell.density                      = fuel_cell.mass/fuel_cell.volume                      
        fuel_cell.specific_power               = fuel_cell.max_power/fuel_cell.mass  
        fuel_cell_stack.mass_properties.mass   = n_total*fuel_cell.mass 
        fuel_cell_stack.voltage                = V_fuel_cell  * series_e
        fuel_cell_stack.maximum_voltage        = V_fuel_cell  * series_e
        fuel_cell_stack.maximum_power          = P_fuel_cell * n_total 
        fuel_cell_stack.maximum_current        = fuel_cell_stack.maximum_power / fuel_cell_stack.maximum_voltage
        fuel_cell_stack.maximum_fuel_flow_rate = m_dot_H2 * n_total         
         
    return 