Source: calc/ecapeCalc.js

import {GetLayerByHght, GetLayerByPres, ConvertToInfo, VirtTemp} from "../calc/skewtCalcs.js"
import { clamp, qc, RadToDeg, QCCheck, ConvertToRaw, celsiusToKelvin } from '../io/general.js';

//First Lets Define Some Constants
export const Constants = {
    sigma: 1.6,
    alpha: 0.8,
    k2: 0.16,
    L_mix: 120,
    Pr: 1/3,
    g: 9.81, //m^2
    c_pd: 1005, //J/(Kg*K)
    c_pv: 1870, //J/(Kg*K)
    c_pl: 4190, //J/(Kg*K)
    c_pi: 2106, //J/(Kg*K)
    R_d: 287.04, //J/(Kg*K)
    R_v: 461.5, //J/(Kg*K)
    L_v_trip: 2501000, //J/kg
    L_i_trip: 333000, //J/kg
    T_trip: 273.15, //K
    phi: 287.04/461.5, //Constants.R_d/Constants.R_v,
    xlv:2501000,// #LATENT HEAT OF VAPORIZATION AT TRIPLE POINT TEMPERATURE
    xls:2834000,// #LATENT HEAT OF SUBLIMATION AT TRIPLE POINT TEMPERATURE

    mgc: 8.314, //molar_gas_constant J/Kelvin/Moles
    amm: 0.029, //avg_molar_mass, Kg/Mole
    dagc: 287, //dry_air_gas_constant
    wvgc: 461.5, //water_vapor_gas_constant
    lhov: 2500000, //latent_heat_of_vaporization
};

/**
 * Return the final element of an array.
 * @param {Array} arr Source array.
 * @returns {*} Last array element, or undefined when the array is empty.
 */
export function LastItem(arr){
    return arr[arr.length - 1];
}

/**
 * Estimate saturation vapor pressure from temperature using the module's polynomial fit.
 * @param {number} temp Temperature (in °C).
 * @returns {number} Saturation vapor pressure (in hPa), or -9999 for an invalid temperature.
 */
function vappres(temp) {
  if (!qc(temp)) return -9999;

  var pol = temp * (1.1112018E-17 + temp * (-3.0994571E-20));
  pol = temp * (2.1874425E-13 + temp * (-1.789232E-15 + pol));
  pol = temp * (4.3884180E-09 + temp * (-2.988388E-11 + pol));
  pol = temp * (7.8736169E-05 + temp * (-6.111796E-07 + pol));
  pol = 0.99999683E-00 + temp * (-9.082695E-03 + pol);
  pol = pol * pol;
  pol = pol * pol;
  return 6.1078 / (pol * pol);
}

/**
 * Calculate saturation mixing ratio from pressure and temperature.
 * @param {number} pres Ambient pressure (in hPa).
 * @param {number} temp Temperature (in °C).
 * @returns {number} Saturation mixing ratio (in g/kg), or -9999 for invalid input.
 */
function mixratio(pres, temp) {
  if (!qc(pres) || !qc(temp)) return -9999;

  var x = 0.02 * (temp - 12.5 + 7500.0 / pres);
  var wfw = 1.0 + 0.0000045 * pres + 0.0014 * x * x;
  var fwesw = wfw * vappres(temp);
  return 621.97 * (fwesw / (pres - fwesw));
}

/**
 * Linearly interpolate between two numeric values.
 * @param {number} a Starting value
 * @param {number} b Ending value
 * @param {number} t Interpolation fraction.
 * @returns {number} Interpolated value
 */
function Lerp(a, b, t){
  return a + (b-a)*t;
}

/**
 * Convert an angle from degrees to radians.
 * @param {number} deg Angle (in degrees).
 * @returns {number} Angle (in radians).
 */
function DegToRad(deg) {
  return deg * (Math.PI / 180);
}

/**
 * Find the interpolation fraction that places a value at g between a and b.
 * @param {number} a Upper endpoint
 * @param {number} b Lower endpoint
 * @param {number} g Target value
 * @returns {number} Interpolation fraction
 */
function GetLerpFactor(a, b, g){
  return (1-(g-b)/(a-b));
}

/**
 * Convert speed and direction angle to Cartesian vector components.
 * @param {number} Spd Vector magnitude (in kt).
 * @param {number} Tta Direction angle (in degrees).
 * @returns {{x: number, y: number}} Cartesian components (in kt).
 */
export function PolarToCartesianDeg(Spd, Tta){
  return {x: Spd * Math.cos(DegToRad(Tta)), y: Spd * Math.sin(DegToRad(Tta))};
}

/**
 * Convert Cartesian vector components to magnitude and direction angle.
 * @param {{x: number, y: number}} T Vector components (in kt).
 * @returns {{r: number, theta: number}} Magnitude (in kt) and angle (in degrees).
 */
export function CartesianToPolarDeg(T){
  return {r: Math.sqrt(Math.pow(T.x, 2) + Math.pow(T.y, 2)), theta: RadToDeg(Math.atan2(T.y, T.x))};
}

/** First sounding row if every field passes QC, else the second row. */
function SurfaceLayerInfo(Sounding){
  let P = Sounding.Slice[0];
  if (QCCheck(P[0]) && QCCheck(P[1]) &&QCCheck(P[2]) &&QCCheck(P[3]) &&QCCheck(P[4]) &&QCCheck(P[5]) && QCCheck(P[6]) ){
    return ConvertToInfo(Sounding.Slice[0], Sounding.Slice[0][0]);
  } else {
    return ConvertToInfo(Sounding.Slice[1], Sounding.Slice[1][0]);
  }
}

/** Lerp every field between rows `Pr` and `Rr`; wind (4 = dir, 5 = speed) via u/v components. */
function LerpSoundingSlice(Pr, Rr, LerpFactor){
  let Slice = {};
  for (let x = 0; x < Pr.length; x++){
    if (x == 4 || x == 5){
      if (x == 5){
        continue;
      }

      let Cartesian = PolarToCartesianDeg(Pr[5], Pr[4]);
      let Cartesian2 = PolarToCartesianDeg(Rr[5], Rr[4]);

      let TrueCart = {
        x: Lerp(Cartesian.x, Cartesian2.x, LerpFactor),
        y: Lerp(Cartesian.y, Cartesian2.y, LerpFactor),
      };

      let TruePolarInterp = CartesianToPolarDeg(TrueCart);

      if (TruePolarInterp.theta < 0){
        TruePolarInterp.theta = (TruePolarInterp.theta + 360) % 360;
      }

      Slice[4] = TruePolarInterp.theta;
      Slice[5] = TruePolarInterp.r;
    } else {
      Slice[x] = Lerp(Pr[x], Rr[x], LerpFactor);
    }
  }
  return Slice;
}



export { ConvertToRaw };

/** Modified Lerpination
 * 
 * @param {*} m1 Number to be (1-V)
 * @param {*} m2 Number to be V
 * @param {*} v V
 * @returns {number} OH YEH
 */
export function ModifLerp(m1, m2, v){
    return (1 - v) * m1 + v * m2;
}

/** entrainment rate in 1/m
 * 
 * @param {*} cape in j/kg
 * @param {*} ecape in j/kg
 * @param {*} ncape in j/kg
 * @param {*} vsr idk
 * @param {*} sch storm coloumn height
 * @returns {number} entrainment rate (M^-1)
 */
export function entrainment_rate(cape, ecape, ncape, vsr, sch){
    const eat = ecape/cape;
    const nt = ncape/cape;
    const vsrt = vsr/Math.sqrt(2 * cape);
    const et = eat - Math.pow(vsrt, 2);

    return (2 * (1 - et)/(et + nt))/sch;
}

/** updraft size en meter
 * 
 * @param {*} erate floating
 * @returns updrafting meters
 */
export function updraft_radius(erate){
    return Math.sqrt(2 * Constants.k2 * Constants.L_mix / (Constants.Pr * erate));
}

/** Specific heat capacity of moist air .
 * 
 * @param {*} spechum in kg/kg i think
 * @returns the HEAT capacity i think
 */
export function spec_heat_cap_of_moist_air(spechum){
    return ModifLerp(Constants.c_pd, Constants.c_pv, spechum);
}

/** PRessure at height whatever
 * 
 * @param {*} pres in mb
 * @param {*} hghtabove in meters
 * @param {*} tmp in KELVIN
 * @returns {number} in mb
 */
export function pres_at_hght(pres, hghtabove, tmp){
    var shght = (Constants.mgc * tmp) / (Constants.amm * Constants.g);
    return pres * Math.exp(-hghtabove/shght);
}

/** Linearly interpolate amazing, morel ike arrayzing :D
 * 
 * @param {*} input_arr 
 * @param {*} output_arr 
 * @param {*} input 
 * @param {*} debug 
 * @returns 
 */
export function linear_interp(input_arr, output_arr, input, debug=false){
     if (input < input_arr[0]){
        return output_arr[0];
    // } else if (input >= input_arr[-1]){
    } else if (input >= LastItem(input_arr)){
        return LastItem(output_arr)
    } else {
        for (let i = 0; i < input_arr.length-1; i++){
            var ip1 = input_arr[i];
            var ip2 = input_arr[i+1];

            if (input == ip1){
                return output_arr[i]
            } else if (input < ip2){
                const op1 = output_arr[i];
                const op2 = output_arr[i + 1];

                const w1 = (ip2 - input)/(ip2 - ip1);
                const w2 = (input - ip1)/(ip2 - ip1);

                return op1 * w1 + op2 * w2;
            } else {
                continue;
            }
        }
    }

    return null;
}

/** Its some sort of linera interpolation but idk and idc!
 * 
 * @param {*} input_arr 
 * @param {*} output_arr 
 * @param {*} input 
 * @param {*} debug 
 * @returns 
 */
export function rev_linear_interp(input_arr, output_arr, input, debug = false){
    //I got no idea but yeah
    if (input > input_arr[0]){
        return output_arr[0];
    } else if (input <= input_arr[-1]){
        return LastItem(output_arr)
    } else {
        for (let i = 0; i < input_arr.length-1; i++){
            var ip1 = input_arr[i];
            var ip2 = input_arr[i+1];

            if (input == ip1){
                return output_arr[i]
            } else if (input > ip2){
                const op1 = output_arr[i];
                const op2 = output_arr[i + 1];

                const w1 = (ip2 - input)/(ip2 - ip1);
                const w2 = (input - ip1)/(ip2 - ip1);

                return op1 * w1 + op2 * w2;
            } else {
                continue;
            }
        }
    }

    return null;
}

/** Specific humidity from Dewpoint
 * 
 * @param {*} prs in hPA i think
 * @param {*} dwp in kelvin maybe idk
 * @returns {number} number
 */
export function spechum_from_dwp(prs, dwp){
    return specHum(prs * 100, vp(dwp) * 100);
}

/** Vapor pressure
 * 
 * @param {*} dwp in kelvin
 * @returns {} in hPA
 */
export function vp(dwpC){
    return 6.112 * Math.exp((17.67 * dwpC) / (dwpC + 243.5));
}

/** SPECIFIC humidity (dislike this thing)
 * @param {*} prs pressure in Pa
 * @param {*} vp vapor pressure in Pa
 * @returns {} in kg m^-3 i think
 */
export function specHum(prs, vp){
    const wvD = absHum(vp, 280);//kg m^-3
    const aD = dry_air_density(prs - vp, 280); //kg m^-3
    return wvD/ (wvD + aD);
}

/** ABSOLUTE humidity (cinema)
 * 
 * @param {*} vp vapor pressure i think PA
 * @param {*} tmp temp in soemthing
 * @returns {number} the ABSOLTUE humdity
 */
export function absHum(vp, tmp){
    return vp/(Constants.wvgc * tmp);
}

/**dry air density, units idk
 * 
 * @param {*} dap dry air pressure Pa ?
 * @param {*} tmp tempereature
 * @returns {number} the density of this jawn
 */
export function dry_air_density(dap, tmp){
    return dap / (Constants.dagc * tmp);
}

/** dewpoiny from spec hum
 * 
 * @param {*} prs Pressure in Pa
 * @param {*} sh Specific hudmity (ew)
 * @returns {number} dew point in Kelvin?
 */
export function dwp_from_spechum(prs, sh){
    return dwp_from_vaporpres(vp_from_spechum(prs, sh));
}

/** vapor pressure from pres and spechiifih
 * 
 * @param {*} prs Pressure in Pa
 * @param {*} sh Specific hudmity (ew)
 * @returns {number} Vapor Pressure in Pa!
 */
export function vp_from_spechum(prs, sh){
    const nmtor = sh * prs;
    const dmtor = (1 / Constants.wvgc + sh / Constants.dagc - sh / Constants.wgvc);
    return nmtor / (Constants.dagc * dmtor);
}


/** DEW POINTY
 * 
 * @param {*} vp VAPOR PRESSURE!!!!!!!!!!!!!!! IN Pa
 * @returns {number} IN KELVIN
 */
export function dwp_from_vaporpres(vp){
    const e0 = 611;
    const t0 = 273.15; //in kevin
    const lhov = 2.5e6; //latent_heat_of_vaporization;

    const vp_nondim = vp;
    const dpr = 1/t0 - (461.5 / lhov) * Math.log(vp_nondim/e0);
    return (1 / dpr)
}

/** HOW MUCh ice in the air :D
 * 
 * @param {*} tmp in kevin idk
 * @param {*} warmest_mixed_phase_temp 273.15
 * @param {*} coldest_mixed_phase_temp 253.15
 */
export function ice_fraction(tmp, warmest_mixed_phase_temp, coldest_mixed_phase_temp){
    if (tmp >= warmest_mixed_phase_temp){
        return 0;
    } else if  (tmp <= coldest_mixed_phase_temp){
        return 1;
    } else {
        return (1/(coldest_mixed_phase_temp - warmest_mixed_phase_temp))*(tmp - warmest_mixed_phase_temp);
    }
}

/** HOW MUCH ICE IS IN THE AIR BRUH!
 * @param {*} tmp in kevin idk
 * @param {*} warmest_mixed_phase_temp 273.15
 * @param {*} coldest_mixed_phase_temp 253.15
 * @returns {number} IN KELVIN
 */
export function ice_fraction_deriv(tmp, warmest_mixed_phase_temp, coldest_mixed_phase_temp){
    if (tmp >= warmest_mixed_phase_temp || tmp <= coldest_mixed_phase_temp){
        return 0;
    } else {
        return (1/(coldest_mixed_phase_temp - warmest_mixed_phase_temp));
    }
}



const r_satCons = {
    vapor_pres_ref: 611.2,// * units("Pa")
    t1: (Constants.c_pv - Constants.c_pi) / Constants.R_v,
    t1a: (Constants.c_pv - Constants.c_pl) / Constants.R_v,
    t2: (Constants.xls - Constants.T_trip * (Constants.c_pv - Constants.c_pi)) / Constants.R_v,
    //t2: (Constants.L_v_trip - Constants.T_trip * (Constants.c_pv - Constants.c_pi)) / Constants.R_v,
    t2a: (Constants.L_v_trip - Constants.T_trip * (Constants.c_pv - Constants.c_pl)) / Constants.R_v,
}

// borrowed and adapted from ECAPE_FUNCTIONS
//temperature, pressure, ice_flag: int, warmest_mixed_phase_temp: pint.Quantity = 273.15 * units("K"), coldest_mixed_phase_temp: pint.Quantity = 253.15 * units("K")
/** something saturated OH I GET IT, basically mixing ratio between SOLID and LIQIUD :D
 * @param {*} tmp like it could be an array but i have no fucking idea lol
 * @param {*} prs 
 * @param {*} ice_flag integer
 * @param {*} warmest_mixed_phase_temp in kelvinm
 * @param {*} coldest_mixed_phase_temp in kevin
 */
export function r_sat(tmp, prs, ice_flag, warmest_mixed_phase_temp=273.15, coldest_mixed_phase_temp=253.15){
    if (ice_flag == 2){
        const t1 = r_satCons.t1;
        const t2 = r_satCons.t2;
        const esi = Math.exp((tmp - Constants.T_trip) * t2/(tmp * Constants.T_trip)) * r_satCons.vapor_pres_ref * Math.pow((tmp/Constants.T_trip), t1)
        return Constants.phi * esi/(prs - esi);
    } else if (ice_flag == 1){
        const omega = ice_fraction(tmp, warmest_mixed_phase_temp, coldest_mixed_phase_temp);

        const qsat_l = r_sat(tmp, prs, 0);
        const qsat_i = r_sat(tmp, prs, 2);

        return (1-omega)*qsat_l + (omega)*qsat_i;
    } else {
        const t1 = r_satCons.t1a;
        const t2 = r_satCons.t2a;
        const esi = Math.exp((tmp - Constants.T_trip) * t2/(tmp * Constants.T_trip)) * r_satCons.vapor_pres_ref * Math.pow((tmp/Constants.T_trip), t1)
        /*if (esi != esi){
          console.error(t1, t2, esi, tmp);
        }*/
        return Constants.phi * esi/(prs - esi);
    }
}

/**
 * Evaluate a step function, using x2 as the value exactly at the threshold.
 * @param {number} x1 Signed distance from the threshold
 * @param {number} x2 Value at the threshold
 * @returns {number} 0, x2, or 1 according to the sign of x1
 */
function heaviside(x1, x2) {
  if (x1 < 0) {
    return 0;
  } else if (x1 === 0) {
    return x2;
  } else {
    return 1;
  }
}

//Idk either
/**
 * Calculate the fractional mixed-phase ice content between two temperature limits.
 * @param {number} T Temperature (in K).
 * @param {number} T1 Warm mixed-phase limit (in K).
 * @param {number} T2 Cold mixed-phase limit (in K).
 * @returns {number} Ice fraction from 0 to 1 (in unitless).
 */
function omega(T, T1, T2){
    return ((T - T1)/(T2 - T1)) * heaviside((T - T1)/(T2 - T1), 1) * heaviside((1 - (T - T1)/(T2-T1)),1) + heaviside(-(1 - (T - T1)/(T2-T1)),1);
}

//Idk
/**
 * Calculate the temperature derivative of the mixed-phase ice fraction.
 * @param {number} T Temperature (in K).
 * @param {number} T1 Warm mixed-phase limit (in K).
 * @param {number} T2 Cold mixed-phase limit (in K).
 * @returns {number} Ice-fraction derivative (in K⁻¹).
 */
function domega(T, T1, T2){
    return (heaviside(T1-T,1) - heaviside(T2-T,1))/(T2-T1);
}

/** Normal Cape
 * 
 * @param {*} T3 C array
 * @param {*} p0 Pa array
 * @param {*} q0 kg/kg array
 * @param {*} z0 m array
 * @param {*} Snding Sounding
 * @param {*} T1 kevin
 * @param {*} T2 kevin
 * @param {*} LFC m
 * @param {*} EL m
 */
export function compute_NCAPE(T0, p0, q0, z0, Snding, T1, T2, LFC, EL){
    // Previous resampling approach rebuilt a 100-m grid from the sounding
    // instead of using the supplied temperature, moisture, pressure, and height arrays.
    // const surfaceHeight = z0[0];
    // for (let height = surfaceHeight; height < EndSlice.H; height += 100) { ... }
    const MSE0 = T0.map((temperature, index) =>
        Constants.c_pd * temperature + Constants.xlv * q0[index] + Constants.g * z0[index]);
    const rsat = T0.map((temperature, index) => r_sat(temperature, p0[index], 0, T1, T2));
    const qsat = rsat.map(mixingRatio => (1 - mixingRatio) * mixingRatio);
    const MSE0_star = T0.map((temperature, index) =>
        Constants.c_pd * temperature + Constants.xlv * qsat[index] + Constants.g * z0[index]);
    const MSE0_bar = new Array(MSE0.length).fill(0);
    MSE0_bar[0] = MSE0[0];

    for (let iz = 1; iz < MSE0_bar.length; iz++){
        let cumulativeMSE = 0;

        // Previous loop omitted the final cumulative interval: x < iz - 1.
        for (let x = 0; x < iz; x++){
            cumulativeMSE += (MSE0[x] + MSE0[x + 1]) * (z0[x + 1] - z0[x]);
        }

        MSE0_bar[iz] = 0.5 * cumulativeMSE / (z0[iz] - z0[0]);
    }

    const int_arg = MSE0_bar.map((mse, index) =>
        -(Constants.g / (Constants.c_pd * T0[index])) * (mse - MSE0_star[index]));
    const nearestHeightIndex = height => z0.reduce((closest, sampleHeight, index) =>
        Math.abs(sampleHeight - height) < Math.abs(z0[closest] - height) ? index : closest, 0);
    const ind_LFC = nearestHeightIndex(LFC);
    const ind_EL = nearestHeightIndex(EL);
    let NCAPE = 0;

    // Python reference integrates through the interval immediately below the nearest EL index.
    for (let i = ind_LFC; i < ind_EL - 1; i++){
        const value = (0.5 * int_arg[i] + 0.5 * int_arg[i + 1]) * (z0[i + 1] - z0[i]);
        if (value === value){
            NCAPE += value;
        }
    }

    NCAPE = Math.max(0, NCAPE);
    return {NCAPE, MSE0_star, MSE0_bar};
}

/**Calculate the entraining cape of a parcel
 * @param {*} sr_wind 0-1 km AGL average storm relative wind magnitude
 * @param {*} psi Parameter defined in eqn. 52, constant for a given equilibrium level
 * @param {*} ncape Buoyancy dilution potential of the free troposphere (eqn. 54)
 * @param {*} cape Convective available potential energy (CAPE, user-defined type)
 * @returns Entraining CAPE (eqn. 55)
 */
export function calc_ecape_a(sr_wind, psi, ncape, cape){
    const ta = Math.pow(sr_wind, 2)/2;
    const tb = ((-1 - psi - (2 * psi / Math.pow(sr_wind, 2)) * ncape) / ((4 * psi) / Math.pow(sr_wind, 2)));
    const tc = (
        Math.sqrt(Math.pow((1 + psi + (2 * psi / Math.pow(sr_wind, 2)) * ncape), 2) + 8 * (psi / Math.pow(sr_wind, 2)) * (cape - (psi * ncape)))
    ) / ((4 * psi) / Math.pow(sr_wind, 2));
    var eCape = ta+tb+tc;

    return (eCape >= 0 ? eCape : 0);
}

//NOW WE DO THE HIGHER END ONES
/** Calculate the mean storm relative (as compared to Bunkers right motion) wind magnitude in the 0-1 km AGL layer
 * 
 * @param {*} pres 
 * @param {*} uwind 
 * @param {*} vwind 
 * @param {*} hght 
 * @param {*} ifb 
 * @param {*} ift 
 * @param {*} CSMot 
 * @param {*} V 
 * @returns {number} storm sr wind in m/s i thinkies
 */
export function calc_sr_wind(pres, uwind, vwind, hght, ifb, ift, CSMot, V){
    const hghtAGL = hght.map(row => row - hght[0]);

    // ecape_parcel supplies x = speed*cos(direction), y = speed*sin(direction).
    // Convert those chart-polar components to meteorological u/v before subtracting storm motion.
    // Previous mismatched-frame calculations:
    // var u_sr = uwind.map(row => row - CSMot.x);
    // var v_sr = vwind.map(row => row + CSMot.y);
    const u_sr = vwind.map(row => -row - CSMot.x);
    const v_sr = uwind.map(row => -row - CSMot.y);
    // Previous integration included segments below ifb and used a fixed 1000 m denominator.
    // var srwind = 0;
    // for (let i = 0; i < hghtAGL.length; i++){
    //     if (hghtAGL[i] > ift) break;
    //     if (hghtAGL[i] <= ift){
    //         srwind += (hghtAGL[i+1] - hghtAGL[i]) / 1000 *
    //             Math.sqrt(Math.pow(u_sr[i], 2) + Math.pow(v_sr[i], 2)) / 1.94384;
    //     }
    // }
    const layerSpeeds = hghtAGL
        .map((height, index) => ({height, index}))
        .filter(({height}) => height >= ifb && height <= ift)
        .map(({index}) => Math.hypot(u_sr[index], v_sr[index]) / 1.94384);

    return layerSpeeds.reduce((sum, speed) => sum + speed, 0) / layerSpeeds.length;
}

/** Parameter defined in eqn. 52, constant for a given equilibrium level, see COMPUTE_ECAPE.m L88 (pitchfork)
 * 
 * @param {*} el meters equilibirum lebel
 * @returns psi
 */
export function calc_psi(el){
    var sigma = 1.1;
    var alpha = 0.8;
    var l_mix = 120; //m
    var pr = 1/3;
    var ksq = 0.18;

    // return (ksq * Math.pow(alpha, 2) * Math.pow(Math.PI, 2) * l_mix) / (pr * Math.pow(sigma, 2) * el)
    return (ksq * Math.pow(alpha, 2) * Math.pow(Math.PI, 2) * l_mix) / (4 * pr * Math.pow(sigma, 2) * el)
}

/**Calculate the entraining CAPE (ECAPE) of a parcel
 * @param {*} hght height Meters
 * @param {*} pres pressure mb
 * @param {*} tmp temp Celsius
 * @param {*} spechum specific humidity
 * @param {*} u_wind x comp of wind
 * @param {*} v_wind y comp of wind
 * @param {*} snding the sounding
 * @param {*} ifb 0 meters
 * @param {*} ift 1000 meters
 * @param {*} sm  right
 * @param {*} passedInfo includes {lfc, el, StormMotion:{u,v}, Cape}
 * @returns 
 */
export function calc_ecape_ncape(hght, pres, tmp, spechum, u_wind, v_wind, snding, ifb=0, ift=1000, sm="right", passedInfo){
    var el_z = passedInfo.EL;
    var lfc_z = passedInfo.LFC;

    const {NCAPE, MSE0_star, MSE0_bar} = compute_NCAPE(tmp.map(celsiusToKelvin), pres.map(row => row*100), spechum, hght, snding, 273.15, 253.15, lfc_z, el_z);
    
    const sr_wind = calc_sr_wind(pres, u_wind, v_wind, hght, ifb, ift, passedInfo.StormMotion, sm)
    const psi = calc_psi(el_z);
    const ECape = calc_ecape_a(sr_wind, psi, NCAPE, passedInfo.Cape);
    const NCape = NCAPE;

    return {ECape, NCape}
}