#Metview Macro

#  **************************** LICENSE START ***********************************
# 
#  Copyright 2019 ECMWF. This software is distributed under the terms
#  of the Apache License version 2.0. In applying this license, ECMWF does not
#  waive the privileges and immunities granted to it by virtue of its status as
#  an Intergovernmental Organization or submit itself to any jurisdiction.
# 
#  ***************************** LICENSE END ************************************
# 

#=============================================================================
# Function      : thermo_parcel_path
#
# Syntax        : 
#                                          
# Category      : THERMODYNAMICS
#
# OneLineDesc   : computes the path of an ascending thermodynamic parcel
#
# Description   : Computes the path of an ascending thermodynamic parcel
#
# Parameters    : 
#           
# Return Value  : 
#
# Dependencies  : none
#
#==============================================================================

function thermo_parcel_path(t: vector, td: vector, p: vector)

	return thermo_parcel_path(t, td, p, (mode: "surface"))

end thermo_parcel_path

function thermo_parcel_path(t: vector, td: vector, p: vector, options: definition)
	
	if options.mode = nil then
	   options.mode = "surface"
	end if
	   
	if not __check_parcel_mode(options) then
    	return nil
    end if
		
	return __compute_parcel_path_core(t, td, p, options)
	
end thermo_parcel_path

function thermo_parcel_path(nc: netcdf)

	return thermo_parcel_path(nc,(mode: "surface"))

end thermo_parcel_path

function thermo_parcel_path(nc: netcdf, options: definition)

    if options.mode = nil then
	   options.mode = "surface"
	end if

    if not __check_parcel_mode(options) then
    	return nil
    end if	
	
	min_level_num = 3
	
	first_dim_val = 1
	if base_language = 'python' then 
	   first_dim_val = 0
	end if
	   
    # get temperature in C
    setcurrent(nc, "t")
    t = values(nc,[first_dim_val,'all'])
    
    # get dewpoint in C
    setcurrent(nc, "td")
    td = values(nc,[first_dim_val,'all'])
     
    # get pressure in hPa
    setcurrent(nc, "pres")
    p = values(nc,[first_dim_val,'all'])
    
    missing_val = 1E+30
    attr=global_attributes(nc)
    if attr._FILL_VALUE <> "" then
        missing_val = attr._FILL_VALUE
    end if    
    
    if count(p) > 0 then
    
    	num = count(p)
    	eps = 1E-3
    	# check for missing values
    	cnt = 1
    	for i=1 to num do   	
    	   if abs(t[i]-missing_val) > eps and abs(td[i]-missing_val) > eps then
    	       t[cnt] = t[i]
    	       td[cnt] = td[i]
    	       p[cnt] = p[i]
    	       cnt = cnt +1
    	   end if    
    	end for
    	
    	if cnt - 1 < min_level_num then
    	   print("Number of levels must be >= ",min_level_num,"! Only ",cnt - 1," found")
    	   return nil
    	end if   
    	
    	if cnt <> num + 1 then
    	   t = t[1,cnt-1]
    	   td = td[1,cnt-1]
    	   p = p[1,cnt-1]
    	   num = cnt - 1
    	end if
    	
    	# we need to be sure the profile goes upwards
    	if p[1] < p[num] then
    	
    		p_r = p
    		t_r = t
    		td_r = td
    		
    		nn=num
    		
    		p=vector(nn)
    		t=vector(nn)
    		td=vector(nn)
    		
    		for i=1 to nn do 
    			p[i] = p_r[num-i+1]
    			t[i] = t_r[num-i+1]
    			td[i] = td_r[num-i+1]
    		end for	
    	end if
  
        #print(t)
        #print(td)
        #print(p)
    
    	return __compute_parcel_path_core(t, td, p, options)
   	end if
   	
   	return nil 

end thermo_parcel_path 
     
#=================================================================     
#     
# PRIVATE FUNCTIONS - SHOULD NOT BE CALLED BY USERS
#
#=================================================================  
  
function __check_parcel_mode(options)

	__THERMO_PARCEL_MODES = ["surface","custom","most_unstable","mean_layer"]
	if options.mode not in __THERMO_PARCEL_MODES then
		print("Invalid thermo parcel mode=",options.mode,". Allowed modes=",__THERMO_PARCEL_MODES)
		return 0
	end if
	return 1
	
end __check_parcel_mode		
   
function __compute_parcel_path_core(t: vector, td: vector, p: vector, options: definition)
	
	# at this point all the checks on the start mode must have been done!!!
	
	# check number of values
	if count(p) = 0 then 
		print("p array is empty")
		return nil
	end if
	
	if count(t) = 0 then 
		print("t array is empty")
		return nil
	end if			
	
	if count(td) = 0 then 
		print("td array is empty")
		return nil
	end if
	
	num = count(p)
	
	if count(t) <> num then
		print("Different number of values in t (",count(t),") and p (",count(p),")")
		return nil	
	end if
	
	if count(td) <> num then
		print("Different number of values in td (",count(td),") and p (",count(p),")")
		return nil	
	end if	
	
	# profile: check input units, must be Celsius and hPa
	
	if t[1] > 60 then
        print("Invalid profile temperature t[1]=",t[1]," C")
        return nil
	end if
	
	if td[1] > 60 then
		print("Invalid profile dewpoint td[1]=",td[1]," C")
        return nil 
    end if	
	
	if p[1] > 1200 then
		print("Invalid profile pressure p[1]=",p[1], " hPa")
        return nil 
	end if			
	
	# converts to SI
    for i=1 to num do   	    	  
        t[i] = t[i] + 273.16
    	td[i] = td[i] + 273.16
    	p[i] = p[i] * 100
    end for
	
	# define the start conditions
	p_start=0
	t_start=0
	td_start=0
	
	# default is surface
	if options.mode = "surface" then
		p_start=p[1]
		t_start=t[1]
		td_start=td[1]
	
	else if options.mode = "custom" then
		p_start=options.start_p * 100
		t_start=options.start_t + 273.16
		td_start=options.start_td + 273.16
    
    else if options.mode = "mean_layer" then		
				
		p_bottom = p[1]
		if options.bottom_p <> nil then		
		  p_bottom=options.bottom_p * 100
		end if
		
		p_top=options.top_p * 100
		
		t_mean=0
		td_mean=0
		p_mean=0
		cnt_mean=0
		for i=1 to num do
			if p[i] >= p_top and p[i] <= p_bottom then
				t_mean = t_mean + t[i]
			 	td_mean = td_mean + td[i]
			 	p_mean = p_mean + p[i]
			 	cnt_mean = cnt_mean + 1
			end if	
		end for
		
		if cnt_mean = 0 then
			print("No profile points found in specified layer (p_bottom = ",p_bottom," hPa, p_top = ",p_top," hPa)")	
			return nil
		end if
			
		p_start=p_mean/cnt_mean
		t_start=t_mean/cnt_mean
		td_start=td_mean/cnt_mean
		
		#print(p_start," ",t_start," ",td_start)
		
	else if options.mode = "most_unstable" then
	
		p_bottom = p[1]
		p_top = 35000 # Pa
		p_delta = 500 # Pa
	
		if options.bottom_p <> nil then		
			p_bottom = options.bottom_p * 100
		end if
		
		if options.top_p <> nil then		
			p_top= options.top_p * 100 
		end if	
		
		if options.delta_p <> nil then		
			p_delta = options.delta_p * 100
		end if
	
		cape = __compute_cape_max(p_bottom, p_top, p_delta, t, td, p)
		if cape.cape = 0  then			
			return nil
		end if	
		
		#print("cape=",cape)
		
		p_start = cape.p
		t_start = cape.t
		td_start = cape.td
		
	end if		
		
	# start conditions: check units 
	
	if t_start < 60 then
		print("invalid start temperature = ",t_start," K")
		return nil
	end if
	
	if td_start < 60 then
		print("invalid start dewpoint = ",td_start," K")
		return nil
	end if	
	
	if p_start < 1200 then
		print("invalid start pressure = ", p_start, " Pa")
		return nil
	end if			
	
	#print("t_start=",t_start)
	#print("td_start=",td_start)
	#print("p_start=",p_start)
	
	stop_at_el = 0
	if options.stop_at_el <> nil then
	   stop_at_el = options.stop_at_el
	end if
	
	return __compute_parcel_path_detailed(t_start, td_start, p_start, options.mode, stop_at_el,
	 t, td, p)

end __compute_parcel_path_core			

function __compute_parcel_path_detailed(t_start_parcel: number, td_start_parcel: number, p_start: number, start_mode: string, stop_at_el: number,t: vector, td: vector, p: vector)
 
	__DO_DEBUG_PARCEL = 0
	
	if __DO_DEBUG_PARCEL then
	   print("t_start_parcel=",t_start_parcel)
	   print("td_start_parcel=",td_start_parcel)
	   print("p_start=",p_start)
	end if   
	# at this point all the checks on the input data must have been checked!!!
	num = count(p)
	
	# compute the LCL
	lcl_parcel = lifted_condensation_level(t_start_parcel, td_start_parcel, p_start)

	if __DO_DEBUG_PARCEL then
		print("lcl=",lcl_parcel)
	end if
	
	# the LCL must exist to continue
	if lcl_parcel = nil then
		print("LCL does not exist")
		return nil
	end if
			
	t_lcl_parcel = lcl_parcel.t
	p_lcl = lcl_parcel.p

	# the LCL must be above (in terms of height) the start pressure
	if p_lcl > p_start then
		print("LCL (=",p_lcl,"Pa) is below start pressure (=",p_start,"Pa)")
		return nil
	end if
	
	# compute the profile temperature at the LCL
	lcl_prof = __compute_profile_at_level(p_lcl, t, p)
	if lcl_prof = nil then
		print("Could not interpolate profile temperature to LCL")
		return nil
	end if	
	
	t_lcl_prof = lcl_prof.t
	idx_lcl = lcl_prof.index

	if __DO_DEBUG_PARCEL then
		print("lcl_prof=",lcl_prof)		
	end if

	cape = nil
	cin = -1
	top = nil
		
	# compute the saturation mixing ratio of the parcel
	mr_lcl_parcel = saturation_mixing_ratio(td_start_parcel,p_start)
		
	# compute the equivalent potential temperature at the LCL. This is an 
	# invariant along the moist adiabat.
	eqpt_parcel = __equivalent_potential_temperature_lcl(t_lcl_parcel,mr_lcl_parcel,p_lcl)	
	if __DO_DEBUG_PARCEL then
		print("eqpt_parcel=",eqpt_parcel,"K")		
	end if
		
	# compute the potential temperature on the dry adiabat
    pt_parcel=potential_temperature(t_start_parcel,p_start)
	if __DO_DEBUG_PARCEL then
		print("pt_parcel=",pt_parcel, "K")		
	end if
		
	# pre-compute the parcel temperature for all the profile levels to 
	# simplify computations later
	t_parcel = t
	for i = 1 to count(p) do
			
		# dry adiabat - below the LCL
		if p[i] > p_lcl then				
			t_parcel[i] = temperature_from_potential_temperature(pt_parcel,p[i])
		# moist adiabat - above the LCL
		else	
			t_parcel[i] = __temperature_from_equivalent_potential_temperature(eqpt_parcel,p[i])			
		end if
	end for		
		
	area_lst =__compute_parcel_areas(t_start_parcel, p_start, p_lcl, pt_parcel, eqpt_parcel, t_parcel, t, p)	
	if __DO_DEBUG_PARCEL then
		__print_parcel_area(area_lst)
	end if
	
	if area_lst <> nil then
	
		# compute CAPE
		cape = __compute_cape(area_lst, p_lcl, t_parcel, t, p)
		if __DO_DEBUG_PARCEL then
			print("cape=",cape)
		end if
	
		if cape <> nil then
			
			# compute TOP
			if stop_at_el <> 1 then
			    if cape.cape > 0 and cape.exit_status = 1 and cape.end_index < count(area_lst) then
				    top = __compute_top(area_lst[cape.end_index+1], cape.cape, eqpt_parcel, t_parcel, t, p)
				    if __DO_DEBUG_PARCEL then
					   print("top=",top)
				    end if
			     end if
			# omit areas above el and do not compute top
			else
			     el = area_lst[cape.end_index].top
		         if el <> nil then
		              area_lst=area_lst[1,cape.end_index]     
		         end if
		    end if          
		    
			# compute CIN
			if cape.cape > 0 and cape.start_index > 1 then
				cin = __compute_cin(area_lst[cape.start_index-1], t_lcl_parcel, p_lcl, t_lcl_prof, idx_lcl, t_parcel, t, p)
				if __DO_DEBUG_PARCEL then
					print("cin=",cin)	
				end if				
			end if	
			
		end if
	end if		
	
	# ------------------------------------
	#  the parcel path curve
	# ------------------------------------
	
	# the parcel path is already defined on the same levels as the profile,
	# but we still need to insert the start, LCL, LFC, EL, TOP and all the
	# other intersections

	t_parcel_lst = nil
	p_parcel_lst = nil
	
	p_top = 0
	if top <> nil then
		p_top = top.p
	else if stop_at_el = 1 and cape <> nil then
	    p_top = area_lst[cape.end_index].top.p
	end if
	
	# the start level
	for i=1 to count(area_lst) do
	
		if p_top <= area_lst[i].bottom.p then	
		
			if i=1 then
				t_parcel_lst = t_parcel_lst & [area_lst[i].bottom.t]
				p_parcel_lst = p_parcel_lst & [area_lst[i].bottom.p]
			end if
		
			for j = area_lst[i].index_start to area_lst[i].index_end do			
			
				if j = idx_lcl then
					t_parcel_lst = t_parcel_lst & [t_lcl_parcel]
					p_parcel_lst = p_parcel_lst & [p_lcl]
				end if	
				
				if p_top <= p[j] then
					t_parcel_lst = t_parcel_lst & [t_parcel[j]]
					p_parcel_lst = p_parcel_lst & [p[j]]
				end if	
			end for	
			
			if p_top <= area_lst[i].top.p then
				t_parcel_lst = t_parcel_lst & [area_lst[i].top.t]
				p_parcel_lst = p_parcel_lst & [area_lst[i].top.p]
			end if	
		
		end if
		
	end for
		
    # convert result to C and hPa		
	for i=1 to count(t_parcel_lst) do
	   t_parcel_lst[i] = t_parcel_lst[i] - 273.16
	   p_parcel_lst[i] = p_parcel_lst[i] / 100
	end for   
	   
	# build the polygons making up the areas		
	
	poly_lst = nil	
	for i=1 to count(area_lst) do
		
		if p_top <= area_lst[i].bottom.p then	
				
			# start from bottom on the parcel side
			t_poly = [area_lst[i].bottom.t]
			p_poly = [area_lst[i].bottom.p]
		
			# goes up along parcel path
			for j = area_lst[i].index_start to area_lst[i].index_end do			
			
				if j = idx_lcl then
					t_poly = t_poly & [t_lcl_parcel]
					p_poly = p_poly & [p_lcl]
				end if	
				
				if p_top <= p[j] then
					t_poly = t_poly & [t_parcel[j]]
					p_poly = p_poly & [p[j]]
				end if
					
			end for	
		
			# add top (parcel side)
			if p_top <= area_lst[i].top.p then
				t_poly = t_poly & [area_lst[i].top.t]
				p_poly = p_poly & [area_lst[i].top.p]
			
				# add top on profile side if area is open on the top
				if area_lst[i].top_prof <> nil then
					t_poly = t_poly & [area_lst[i].top_prof.t]
					p_poly = p_poly & [area_lst[i].top_prof.p]
				end if
		    end if
		    
		    # goes down along profile
			for j = area_lst[i].index_end to area_lst[i].index_start by -1 do			
				if p_top <= p[j] then
					t_poly = t_poly & [t[j]]
					p_poly = p_poly & [p[j]]
				end if				
			end for	
			
			# add bottom on profile side if area is open at the bottom
			if area_lst[i].bottom_prof <> nil then
				t_poly = t_poly & [area_lst[i].bottom_prof.t]
				p_poly = p_poly & [area_lst[i].bottom_prof.p]
			end if
	       
	        # convert result to C and hPa		
	        for j=1 to count(t_poly) do
	           t_poly[j] = t_poly[j] - 273.16
	           p_poly[j] = p_poly[j] / 100
	        end for     
		
			poly_lst = poly_lst & [ [(t: t_poly, p: p_poly, positive: area_lst[i].positive)]]
		end if
		
	end for	
	
	# create the resulting definition
	
	res=(t: t_parcel_lst, 
	   p: p_parcel_lst,
	   area: poly_lst, 
	   lcl: __tp_point_to_c_hpa(lcl_parcel),
	   cape: -1,
	   cin: cin,
	   lfc: nil,
	   el: nil,
	   top: nil,
	   start: (t: t_start_parcel - 273.16,
	           td: td_start_parcel - 273.16,
	           p: p_start / 100,
	           mode: start_mode)
	)
	 

	if cape <> nil then
		res.cape = cape.cape		
		res.lfc = __tp_point_to_c_hpa(area_lst[cape.start_index].bottom)
		res.el = area_lst[cape.end_index].top
		if res.el <> nil then
		  res.el = __tp_point_to_c_hpa(res.el)		
		end if
	end if	
	
	if top <> nil then
		res.top = __tp_point_to_c_hpa((t: top.t_parcel, p: top.p))
    end if		
	
	if __DO_DEBUG_PARCEL then
		__print_parcel_result(res)
	end if
	
	return res

end __compute_parcel_path_detailed

#=====================================================================================
# Compute the Level of Free Convection (LFC) and Equilibrium (EL) levels above 
# the LCL using the parcel method. 
# An LFC is defined as the intersection of the parcel path and the profile when the parcel
# is colder than the profile below the intersection. This is the starting point of a
# "positive area" where the parcel is warmer than the profile. An EL is defined as the
# intersection of the parcel path and the profile when the parcel is warmer than the profile
# below the intersection. This is the starting point of a "negative area" where the parcel
# is colder than the profile. If a positive area is closed on the top it ends in an EL. 
# Likewise if a negative are is closed on the top it ends in an LFC. The topmost area 
# (can be postive or negative) can be open on the top!
#
# Input: 
#		t_start: the start temperature of the parcel (K)
#       p_start: the start pressure of the parcel (Pa)
#       p_lcl: the pressure at the LCL (Pa)
#       pt_parcel: the potential temperature of the parcel along the
#                    dry adiabat below the LCL (K)
#       eqpt_parcel: the equivalent potential temperature of the parcel along the
#                    moist adiabat  above the LCL (K)
#       
#		t_parcel: the temperature along the parcel path on the profile's levels (K)
#       t: the temperature profile (K)
#		p: the pressure profile (Pa)
# Return:
#       -list of definitions of the following format:
#          (positive: 1 if it as a positive area, otherwise it is a negative area
#		    bottom: the bottom point (t,p) of the area on the parcel side 
#           bottom_prof: the bottom point (t,p) of the area on the profile side. If the area is closed
#                        at the bottom it is nil. 
#           top: the top point (t,p) of the area on the parcel side
#           top_profile: the top point (t,p) of the area on the profile side. If the area is closed
#                        at the top it is nil.
#           index_start: the index of the first level in the given area
#           end_index: the index of the last level in the given area           
#		-on error nil is returned
#==============================================================================

function __compute_parcel_areas(t_start, p_start, p_lcl, pt_parcel: number, eqpt_parcel: number, t_parcel: vector, t: vector, p: vector)
		
	num = count(p)
	
	if num <= 2 then
		#fail("num <= 2")
		return nil
	end if		
	
	# if all levels are above the start level there are no areas
	if p_start > p[1] + 1E-5 then
		return nil
	end if	
		
	# find first index above (or on) the start level
	i = 1
	while i <= num and p[i] - p_start > 1E-3 do
		i =  i + 1
	end while	
	
    # if all the levels are below the start level, there are no areas
	if i > num then
		return nil
	end if	
	
	# this is the first index above (or on) the start level
	idx_start = i
	
	# detect if start is on a profile level
	start_is_level = 0	
	if abs(p_start - p[idx_start]) < 1E-3 then
		start_is_level =  1
	end if	
	
	# sanity check
	if start_is_level = 0 and idx_start = 1 then
		return nil
	end if	
	
	t_start_prof = t[idx_start]
		
	# if the start is not on a profile level we need to 
	# interpolate the profile onto it	
	if start_is_level = 0 then
	
		# compute the profile temperature on the start level
		prof_start = __compute_profile_at_level(p_start, t, p)
		if prof_start = nil then
			return nil
		end if	
		t_start_prof = prof_start.t
	end if
		
	# create object for the firt area - its type may not be known at this time
	# if the parcel starts from the profile				
	area = __make_parcel_area()
	area.bottom = (t:t_start, p:p_start)
	area.index_start =idx_start
	area.positive = -1 #unknown
		
	# parcel is warmer than the profile  - positive area 
	if  t_start - t_start_prof > 1E-5 then 
	
		area.positive = 1
		area.bottom_prof = (t:t_start_prof, p:p_start)
	
	# parcel is colder than the profile - negative area
	else if t_start_prof - t_start > 1E-5 then 
	
		area.positive = 0
		area.bottom_prof = (t:t_start_prof, p:p_start)
	
	end if
	
	# if the start is on a profile level we start the computations form the level above
	if start_is_level then
		idx_start = idx_start + 1
	end if	
	
	# sanity check
	if idx_start <= 1 then
		return nil
	end if	
	
	area_lst = nil
	
	for i=idx_start to num do					
				
		# we are in a positive area
		if t_parcel[i] > t[i] then
			
			# we just left a negative area and start a new positive area	
			if area.positive = 0 then
				
				# find intersection point of parcel path and profile.
			
				sec =nil
				# on the dry adiabat
				if p[i] > p_lcl then				
					sec = __find_dry_intersection(t[i-1],t[i],p[i-1],p[i],pt_parcel,1)
				# on the moist adiabat (this is an LFC)	
				else
					sec = __find_moist_intersection(t[i-1],t[i],p[i-1],p[i],eqpt_parcel,1)
				end if
				 
				# add top to the previous area and add it to the area list 	
				area.top = sec
				area.index_end = i-1
				area_lst = area_lst & [area] 	
				 	
				# create a new positive current area 			
				area = __make_parcel_area()
				area.bottom = sec
				area.index_start = i
				area.positive = 1
				
			# we not yet determined the are type					
			else if area.positive = -1 then
				area.positive = 1
			end if	
						
		# we are in a negative area 
		else if t_parcel[i] <= t[i] then
		
			# we just left a positive area and start a new negative area
			if area.positive = 1 then
			
				# find intersection point of parcel path and profile.
				
				# on the dry adiabat
				if p[i] > p_lcl then				
					sec = __find_dry_intersection(t[i-1],t[i],p[i-1],p[i],pt_parcel,-1)
				# on the moist adiabat (this is an EL)	
				else
					sec = __find_moist_intersection(t[i-1],t[i],p[i-1],p[i],eqpt_parcel,-1)
				end if
				
		    	# add top to the previous area and add it to the area list 	
				area.top = sec
				area.index_end = i-1
				area_lst = area_lst & [area] 	
				 	
				# create a new negative current area 					
			    area = __make_parcel_area()
				area.bottom = sec
				area.index_start = i
				area.positive = 0
				
			# we not yet determined the are type					
			else if area.positive = -1 then
				area.positive = 0
			end if			
			
		end if			
	end for 
	
	# add last area
	
	top_index= num
	if area.index_start <> top_index then
		area.top = (t:t_parcel[top_index], p: p[top_index])
		area.top_prof = (t: t[top_index],p: p[top_index])
		area.index_end = top_index
		area_lst = area_lst & [area]
	end if	
	
	return area_lst
	
end __compute_parcel_areas


#=====================================================================================
# Compute the Convective Available Potential Energy (CAPE) using the parcel method.
# CAPE is the integral of buoyancy through the positive area between the LFCs and ELs 
# bounded by the parcel path and the profile
#
# Input: 
#		areas: the list of the areas
#       p_lcl: the pressure at the LCL (Pa)
#		t_parcel: the temperature along the parcel path on the profile's levels (K)
#       t: the temperature profile (K)
#		p: the pressure profile (Pa)
# Return:
#       -a definition with the following members:
#          cape: the value of CAPE (J/kg)
#          start_index: the index of the (positive) area where the CAPE computation started
#          end_index: the index of the (positive) area where the CAPE computation finished
#          exit_status: 1 if the computation ended at the top of an area, otherwise 
#                      the computation stopped before reaching the top of the area       
#		-on error 0 is returned
#==============================================================================

function __compute_cape(areas: list, p_lcl: number, t_parcel: vector,t: vector, p: vector)
					
	# gas constant
	Rd = 287.05 # J/kg/K
	
	cape = 0.0
	area_start_index = 0
	area_end_index = 0
	exit_status = 0
	
	# we need at least one area
	if count(areas) = 0 then
		return nil
	end if
	
	for i=1 to count(areas) do
		
		# we only use positive areas when they end above the LCL and
		# start below 100 hPa
		if areas[i].positive = 1 and 
			areas[i].top.p <= p_lcl and areas[i].bottom.p > 10000 then
				
			if area_start_index = 0 then	
				area_start_index = i
			end if
			
			area_end_index = i
			exit_status = 1
							
			lfc = areas[i].bottom
			t_lfc =	lfc.t
			p_lfc = lfc.p	
			t_lfc_prof = t_lfc
			# the area can be open at the bottom
			if areas[i].bottom_prof <> nil then
				t_lfc_prof=areas[i].bottom_prof.t
			end if	
			
			el = areas[i].top	
			t_el = el.t
			p_el = el.p
			t_el_prof = el.t
			# the area can be open at the top
			if areas[i].top_prof <> nil then
				t_el_prof=areas[i].top_prof.t
			end if	
			
			idx_start = areas[i].index_start
			idx_end = areas[i].index_end
		
			# we start at the LFC	
			p1 = p_lfc
			p2 = p1	
			t1 = t_lfc_prof
			t2 = t1		
			
			t_parcel_1 = t_lfc
			t_parcel_2 = t_parcel_1
	
			for j=idx_start to idx_end+1 do
	
				# we only do the CAPE computations up to 50 hPa (~ 21 km)
				if p1 >= 5000 then
	
					# we are below the EL
					if j <> idx_end+1 then
						p2 = p[j]
						t2 = t[j]
						#the parcel is on a moist adiabat - its temperature is already computed
						t_parcel_2 = t_parcel[j]					
					
					# the last point is the EL
					else 
						p2 = p_el
						t2 = t_el_prof
						t_parcel_2 = t2
					end if
	
					dlnp = log(p2) - log(p1)
					dt1 =  t_parcel_1 - t1
					dt2 =  t_parcel_2 - t2
					cape = cape - Rd * dlnp * (dt2 + dt1) / 2.0
		
					p1 = p2
					t1 = t2
					t_parcel_1 = t_parcel_2						    
		   		else
		   			exit_status = 0
		   		end if
		   		
			end for								
		end if
	end for
	
	if area_start_index = 0 then
		return nil
	end if	
	
	return (cape: cape, start_index: area_start_index, end_index: area_end_index, exit_status: exit_status)
	
end __compute_cape
		
#===============================================================
# Compute the maximum cape in a given layer
#===============================================================
function __compute_cape_max(p_bottom, p_top, p_delta, t, td, p)

	num = count(p)

	#print(p_bottom," ",p_top," ",p[1])

	cape_max = 0 
	t_cape= 0
	td_cape = 0
	p_cape = 0
			
	eps = 1E-4		
				
	for i=1 to num do
		
		if p_bottom + eps >= p[i] and p_top - eps <= p[i] then
			t_start_parcel  = t[i]
			td_start_parcel = td[i]
			p_start = p[i]
			
			#print(t_start_parcel," ", td_start_parcel," ", p_start)
			
			cape = __compute_cape_only(t_start_parcel, td_start_parcel, p_start, p_top, p_delta, t, td, p)
			
			#print("CAPE=",cape," ",t_start_parcel," ", td_start_parcel," ", p_start)
			
			if cape > cape_max then
				cape_max = cape
				t_cape= t_start_parcel
				td_cape = td_start_parcel
				p_cape = p_start
			end if	
			
		end if	
	end for
	
	return (cape: cape_max, t: t_cape, td: td_cape, p: p_cape)
		
end __compute_cape_max

function __compute_cape_only(t_start_parcel, td_start_parcel, p_start, p_top, p_delta, t, td, p)			
		
	cape = 0				
	
	# compute the LCL
	lcl_parcel = lifted_condensation_level(t_start_parcel, td_start_parcel, p_start)

	#if __DO_DEBUG_PARCEL then
	#	print("lcl=",lcl_parcel)
	#end if
	
	# the LCL must exist to continue
	if lcl_parcel = nil then
	#	print("LCL does not exist")
		return cape
	end if

	t_lcl_parcel = lcl_parcel.t
	p_lcl = lcl_parcel.p

	# the LCL must be above (in terms of height) the start pressure
	if p_lcl > p_start then
		#print("LCL (=",p_lcl,"hPa) is below start pressure (=",p_start,"hPa)")
		return cape
	end if
	
	# compute the profile temperature at the LCL
	lcl_prof = __compute_profile_at_level(p_lcl, t, p)
	if lcl_prof = nil then
		#print("Could not interpolate profile temperature to LCL")
		return cape
	end if	
	
	t_lcl_prof = lcl_prof.t
	idx_lcl = lcl_prof.index

	#if __DO_DEBUG_PARCEL then
	#	print("lcl_prof=",lcl_prof)		
	#end if

	# compute the saturation mixing ratio of the parcel
	mr_lcl_parcel = saturation_mixing_ratio(td_start_parcel,p_start)
		
	# compute the equivalent potential temperature at the LCL. This is an 
	# invariant along the moist adiabat.
	eqpt_parcel = __equivalent_potential_temperature_lcl(t_lcl_parcel,mr_lcl_parcel,p_lcl)	
	#if __DO_DEBUG_PARCEL then
	#	print("eqpt_parcel=",eqpt_parcel, " K")		
	#end if
		
	# compute the potential temperature on the dry adiabat
    pt_parcel=potential_temperature(t_start_parcel,p_start)
	#if __DO_DEBUG_PARCEL then
	#	print("pt_parcel=",pt_parcel, " K")		
	#end if
	
	# pre-compute the parcel temperature for all the profile levels to 
	# simplify computations later
	t_parcel = t
	for i = 1 to count(p) do
			
		# dry adiabat - below the LCL
		if p[i] > p_lcl then				
			t_parcel[i] = temperature_from_potential_temperature(pt_parcel,p[i])
		# moist adiabat - above the LCL
		else	
			t_parcel[i] = __temperature_from_equivalent_potential_temperature(eqpt_parcel,p[i])
		end if
	end for		
		
	area_lst =__compute_parcel_areas(t_start_parcel, p_start, p_lcl, pt_parcel, eqpt_parcel, t_parcel, t, p)	
	#if __DO_DEBUG_PARCEL then
	#	__print_parcel_area(area_lst)
	#end if
	
	if area_lst <> nil then
	
		# compute CAPE
		cape_res = __compute_cape(area_lst, p_lcl, t_parcel, t, p)
		if cape_res <> nil then
			cape = cape_res.cape
		#if __DO_DEBUG_PARCEL then
		#	print("cape=",cape)
		#end if		
		end if	
	end if
	
	return cape

end __compute_cape_only
	
#=====================================================================================
# Compute the the uppermost level (TOP) an ascending parcel can reach based on the parcel method.
# This is the level in the last (and open) "negative buoyancy area" where the negative buoyancy 
# energy equals the CAPE. 
#
# Input: 
#		areas: the neagtive are where the TOP computation has to be done
#       cape: the CAPE value (J/kg)
#       eqpt_parcel: the equivalent potential temperature of the parcel along the 
#                    moist adiabat (K)
#		t_parcel: the temperature along the parcel path on the profile's levels (K)
#       t: the temperature profile (K)
#		p: the pressure profile (Pa)
# Return:
#       -a definition with the following members:
#        	p: the pressure of TOP (Pa)
#           t_prof: the temperature of the profile at TOP
#           t_parcel: the temperature of the parcel at TOP
#           index_top: the index of the profile level just above the TOP         
#		-on error or if the TOP does not exist nil is returned
#==============================================================================	

function __compute_top(area, cape: number, eqpt_parcel: number, t_parcel: vector,t: vector, p: vector)
					
	# gas constant
	Rd = 287.05 # J/kg/K
	
	# we need a valid CAPE
	if cape <= 0 then
		return nil
	end if
	
	p_top = -1
	t_top_prof = -1
	t_top_parcel = -1
	idx_top = 0	
	energy = 0.0
	
	# the start point is the EL
    el = area.bottom
	t_el = el.t
	p_el = el.p
	
	if area.bottom_prof <> nil then
		return nil
	end if	
		
	idx_start = area.index_start
	idx_end = area.index_end
		
	if idx_end <= 0 then
		return nil
	end if	
			
	# we start at the EL	
	p1 = p_el
	p2 = p1	
	t1 = t_el
	t2 = t1		
	t_parcel_1 = t1
	t_parcel_2 = t_parcel_1
	
	for i=idx_start to idx_end do
		
		if idx_top = 0 then
		
			p2 = p[i]
			t2 = t[i]
			#the parcel is on a moist adiabat - its temperature is already computed
			t_parcel_2 = t_parcel[i]					
	
			# compute buoyancy (energy)
			dlnp = log(p2) - log(p1) # < 0
			dt1 =  t_parcel_1 - t1 # <=0
			dt2 =  t_parcel_2 - t2 # <=0
			de = Rd * dlnp * (dt2 + dt1) / 2.0
			energy = energy + de
		
			#print("energy ",p2," ",energy)
		
			p1 = p2
			t1 = t2
			t_parcel_1 = t_parcel_2						    
		   
		    # when the accumulated energy is larger than the CAPE we are just above the TOP
			if energy >= cape then
				idx_top = i
				de_target = cape - (energy - de) 				
			end if	    
		end if									
	end for
	
	# there is a top - we only knows that it is between two pressure levels.
	# Now we need to determine its exact pressure!
	if idx_top <> 0 then
	
		# we find the top pressure by iteration
		iter_num=10
    	eps=0.25
		
		# start with a bottom half of the given layer
		dp = (p[idx_top-1]-p[idx_top])/2
	
		p1 = p[idx_top-1]
		p2 = p1-dp
		t1 = t[idx_top-1]
		t2 = __logp_interpolation(t1, t[idx_top], p1, p[idx_top], p2)		
		t_parcel_1 = t1
		t_parcel_2 = __temperature_from_equivalent_potential_temperature(eqpt_parcel,p2)
		top_found = 0
		
		for i=1 to iter_num do 
		 
		 	if top_found = 0 then
		 
		 		# compute the buoyancy in the layer
				dlnp = log(p2) - log(p1) # <0
				dt1 =  t_parcel_1 - t1 # <= 0
				dt2 =  t_parcel_2 - t2 # <=0
				de = Rd * dlnp * (dt2 + dt1) / 2.0
			
				# check convergence
				if abs(de-de_target) < eps or i=iter_num then
					p_top = p2
					t_top_prof = t2
					t_top_parcel = t_parcel_2
					top_found = 1
				
				# adjust the top pressure of the layer
				else					
			 		# decrease pressure delta as we converge, dp is always positive
        	 		dp = dp / 2.0
						
					if de < de_target then
						p2 = p2 - dp # moving up
					else
						p2 = p2 + dp #moving down	
					end if
					
					t2 = __logp_interpolation(t1, t[idx_top], p1, p[idx_top], p2)	
        	 		t_parcel_2 = temperature_from_potential_temperature(eqpt_parcel,p2)
		
				 end if
			end if		
		end for
		
		return (p:p_top, t_prof: t_top_prof, t_parcel: t_top_parcel, index_top: idx_top)
		
	end if		
	
	return nil
	
end __compute_top		
	
#===============================================================================================
# Compute the Convective Inhibition (CIN). This is the negative buoyancy energy the ascending
# parcel has to overcome to reach the LFC.  
#
# Input: 
#		area: the negative buoyancy area where the CIN is computed, it must
#             contain the LCL       
#       t_lcl_parcel: the parcel's temperature at the LCL (K)
#       p_lcl: the pressure at the LCL (Pa)
#       t_lcl_prof: the profile's temeparture at the LCL (K)
#       idx_lcl: the index of the profile level just above the LCL 
#		t_parcel: the temperature along the parcel path on the profile's levels (K)
#       t: the temperature profile (K)
#		p: the pressure profile (Pa)
# Return:
#       the value of CIN (J/kg) (>=0)
#=============================================================================================		

function __compute_cin(area, t_lcl_parcel: number, p_lcl: number, t_lcl_prof: number, idx_lcl: number, 
						t_parcel: vector, t: vector, p: vector)
	
	# gas constant
	Rd = 287.05 # J/kg/K
	
	# we need a real positive area
	#if pos_area then
	#	return nil
	#end if
	
	cin = 0.0
	
	p1 = area.bottom.p
	p2 = p1
	
	# the bottom can be open
	t1 = area.bottom.t
	if area.bottom_prof <> nil then
		t1 = area.bottom_prof.t
	end if	
	t2 = t1
		
	t_parcel_1 = area.bottom.t
	t_parcel_2 = t_parcel_1
	
	idx_start = area.index_start
	
	# first step: we integrate from the start up to the LCL. 
	# The level defined by idx_lcl is above the LCL!
	for i=idx_start to idx_lcl do
		
		# we are below the LCL		
		if i <> idx_lcl then
			p2 = p[i]
			t2 = t[i]
			t_parcel_2 = t_parcel[i]			
		# the last point is the LCL
		else 
			p2 = p_lcl
			t2 = t_lcl_prof
			t2_parcel_2 = t_lcl_parcel
		end if
			
		dlnp = log(p2) - log(p1) # < 0
		dt1 =  t_parcel_1 - t1 # <= 0
		dt2 =  t_parcel_2 - t2 # <= 0
		cin = cin + Rd * dlnp * (dt2 + dt1) / 2.0
		
		#print(p1," ",t_parcel_1," ",t1)
		#print(" ",p2," ",t_parcel_2," ",t2," ", cin)
		  		
		p1 = p2
		t1 = t2
		t_parcel_1 = t_parcel_2
		
	end for	
		
	# the LFC
	t_lfc = area.top.t
	p_lfc = area.top.p
					
	# the first profile level above the LFC
	idx_lfc = area.index_end+1
	
	p1 = p_lcl
	t1 = t_lcl_prof
	t2 = t1
	
	t_parcel_t1 = t_lcl_parcel
	t_parcel_t2 = t1
		
	# second step: we integrate from the LCL up to the LFC. The level defined by idx_lcl is above the LCL!
	# The level defined by idx_lfc is above the LFC!
	for i=idx_lcl to idx_lfc do
		
		if i <> idx_lfc then
			p2 = p[i]
			t2 = t[i]	
			t_parcel_2  = t_parcel[i]	
		else # the last point is the LFC
			p2 = p_lfc
			t2 = t_lfc		
			t2_parcel_2 = t_lfc
		end if
		
		dlnp = log(p2) - log(p1) # < 0
		dt1 =  t_parcel_1 - t1 # <=0
		dt2 =  t_parcel_2 - t2 # <= 0
		cin = cin + Rd *dlnp * (dt2 + dt1) / 2.0
		
		#print(p1," ",t_parcel_1," ",t1)
		#print(" ",p2," ",t_parcel_2," ",t2," ", cin)
		
		p1 = p2
		t1 = t2
		t_parcel_1 = t_parcel_2		 
										
	end for	
	
	return cin
	
end __compute_cin

function __make_parcel_area()

	return (bottom: nil, bottom_prof: nil, top: nil, top_prof: nil, index_start: 0, index_end: 0, positive: -1)	

end __make_parcel_area

#=============================================================================
# Compute the intersection of a temperature profile in a given layer and a 
# moist adiabat defined by its equivalent potential temperature.
#
# Input: 
#		t1,t2: the temperatures on the layer's boundaries (K)
#       p1,p2: the pressures on the layers's boundaries (Pa)
#       eqpt: the equivalent potential temperature (K)
#       dp_sign: 
# Return:
#       a definition of (t,p) containing the temperature (K) and the pressure (Pa)
#       of the intersection             
#==============================================================================

function __find_moist_intersection(t1,t2,p1,p2,eqpt,dp_sign)
	
	dp = (p1 - p2)/2.0
	p = p2 + dp
	
	eps = 1E-5
	iter_num = 12
	
	for i=0 to iter_num do
		t = __logp_interpolation(t1,t2,p1,p2,p)		
		t_parcel = __temperature_from_equivalent_potential_temperature(eqpt,p)
		
		dp = dp / 2.0
		if abs(t_parcel - t) < eps or i=iter_num then
			return (t:t,p:p)
		else if t_parcel > t then
			p = p + dp * dp_sign
		else 
			p = p - dp * dp_sign
		end if		
	end for
	
	return (t:t,p:p)		

end __find_moist_intersection

#=============================================================================
# Compute the intersection of a temperature profile in a given layer and a 
# dry adiabat defined by its potential temperature.
#
# Input: 
#		t1,t2: the temperatures on the layer's boundaries (K)
#       p1,p2: the pressures on the layers's boundaries (Pa)
#       pt: the potential temperature (K)
#       dp_sign: 
# Return:
#       a definition of (t,p) containing the temperature (K) and the pressure (Pa)
#       of the intersection             
#==============================================================================

function __find_dry_intersection(t1,t2,p1,p2,pt,dp_sign)
	
	dp = (p1 - p2)/2.0
	p = p2 + dp
	
	eps = 1E-5
	iter_num = 12
	
	for i=0 to iter_num do
		t = __logp_interpolation(t1,t2,p1,p2,p)		
		t_parcel = temperature_from_potential_temperature(pt,p)
		
		dp = dp / 2.0
		if abs(t_parcel - t) < eps or i = iter_num then
			return (t:t,p:p)
		else if t_parcel > t then
			p = p + dp * dp_sign
		else 
			p = p - dp * dp_sign
		end if		
	end for
	
	return (t:t,p:p)		

end __find_dry_intersection
	
#=============================================================================
# Compute the temperature on a given pressure level in a profile using 
# logartihmic pressure interpolation.
#
# Input: 
#		p_level: the target pressure level (Pa)
#       t: the temperature profile (K)
#		p: the pressure profile (Pa)
# Return:
#       -a definition of (t,index) containing the temperature (K) on the target level and 
#        the index of the profile just above the target level           
#		-on error nil is returned
#==============================================================================

function __compute_profile_at_level(p_level: number, t: vector, p: vector)
		
	num = count(p)
	
	if num <= 2 then
		return nil
	end if		
			
	# the target level cannot be below the first profile level 
	if p_level > p[1] then
		return nil
	end if
	
	# find the first level above the target level
	idx = 0
	while p_level < p[idx+1] do
		idx = idx + 1	
	end while

	# if all levels are below the target level 
	if idx = 0 then		
		return nil
	end if	
	
	idx = idx + 1
	
	# interpolate profile to target level
	t_prof=__logp_interpolation(t[idx], t[idx-1], p[idx], p[idx-1], p_level)	
	
	return (t:t_prof, index: idx)

end __compute_prof_at_level	

#=============================================================================
# Compute the value of a given parameter on a given pressure level in a
# given layer using logartihmic pressure interpolation.
#
# Input: 
#		x1,x2: the parameter's values at the layer's boundaries
#       p1,p2: the pressure at the layer's boundaries (Pa)
#		p: the target pressure (Pa)
# Return:
#       the parameter's value at the given pressure level 
#==============================================================================

function __logp_interpolation(x1, x2, p1, p2, p)

	if  x1 = x2 or p1 = p2 then
		return x1
	end if

	return x1 + (x2-x1) * log(p/p1)/log(p2/p1)

end __logp_interpolation

#=================================================================================
# Compute the equivalent potential temperature from the parcel's state at the LCL.
#
# Input: 
#       t_lcl: the parcel's temperature at the LCL (K)
#		mr_lcl: the parce's saturation mixing ration at the LCL (kg/kg)
#       p_lcl: the pressure at the LCL (Pa)
# Return:
#       the equivalent potential temperature (K)           
#==============================================================================

function __equivalent_potential_temperature_lcl(t_lcl: number, mr_lcl: number,p_lcl: number)

    b=2.674456
    
    t_lcl_kelvin = t_lcl
    
    return potential_temperature(t_lcl,p_lcl)*exp(b*1000*mr_lcl/t_lcl_kelvin)
	
end __equivalent_potential_temperature_lcl	


function __tp_point_to_c_hpa(point: definition)

    return (t: point.t - 273.16,p: point.p / 100)

end  __tp_point_to_c_hpa

function __print_parcel_area(v)
		
	for i = 1 to count(v) do
		print("AREA: ",i)
		print(" positive=",v[i].positive)
		print(" bottom=",v[i].bottom)
		print(" bottom_prof=",v[i].bottom_prof)
		print(" top=",v[i].top)
		print(" top_prof=",v[i].top_prof)
		print(" index_start=",v[i].index_start)
		print(" index_end=",v[i].index_end)		
	end for
	
end __print_parcel_area		

function __print_parcel_result(r)
		
    print("RESULT:")
    aStr=""
	if r.area <> nil then 
		loop a in r.area
			aStr = aStr & a.positive
		end loop
	end if
			
	print(" areas=(",count(r.area),") ",aStr)
	print(" cape=",r.cape)
	print(" cin=",r.cin)
	print(" lcl=",r.lcl)
	print(" lfc=",r.lfc)
	print(" el=",r.el) 
	print(" top=",r.top)
	
end __print_parcel_result	

function __print_parcel_result_for_test(r)
			
	print(" cape: ",r.cape,",")
	print(" cin: ",r.cin,",")
	print(" lcl: ",r.lcl,",")
	print(" lfc: ",r.lfc,",")
	print(" el: ",r.el,",") 
	print(" top: ",r.top,",")
		
	print(" area_count: ",count(r.area),",")
	aStr=""
	if r.area <> nil then 
		loop a in r.area
			aStr = aStr & a.positive
		end loop
	end if
	print(" area_type: ",aStr,",")
	
	print(" t_count: ",count(r.t),",")
	print(" p_count: ",count(r.p),",")
	
	num = count(r.t) 
	if num > 0 then
	   print(" t_first: ",r.t[1],",")
	   print(" t_last: ",r.t[num],",")
	   print(" t_mean: ",mean(vector(r.t)),",")
	end if
	   
	num = count(r.p) 
	if num > 0 then
	   print(" p_first: ",r.p[1],",")
	   print(" p_last: ",r.p[num],",")
	   print(" p_mean: ",mean(vector(r.p)))
	end if
	
end __print_parcel_result_for_test