libEOS83.pl
author Andreas Thurnherr <ant@ldeo.columbia.edu>
Mon, 13 Apr 2020 11:06:22 -0400
changeset 40 c1803ae2540f
parent 4 ff72b00b4342
permissions -rw-r--r--
.

#======================================================================
#          L I B E O S 8 3 . P L 
#          doc: Mon Mar  8 08:22:05 1999
#          dlm: Tue Feb  5 16:42:28 2013
#          (c) 1999 A.M. Thurnherr
#          uE-Info: 20 59 NIL 0 0 72 2 2 4 NIL ofnI
#======================================================================

# Perl Implementation of UNESCO Eqn of State 1983

# Notes:
# 	- copied from eos83calc.c which was in turn...
#	- copied from pexec_v4 (incl comments)
#	- only subset of functions implemented
#	- some attempt at cleaning up the code has been made
#	- pressures in dbar throughout
#	- no temperature scale assumed; set PARAM ITS=90|68
#	- T90*1.00024=T68
#	- check values calculated with T68
#	- no conductivity unit assumed; set PARAM cond.unit=S/m|mS/cm

# HISTORY:
#	Mar 08, 1999: - translated by hand from krc
#				  - added &alpha(), &beta(), &Rrho(), &TurnerAngle()
#	Mar 13, 1999: - cosmetic changes
#	Mar 14, 1999: - BUG NAN instead of NaN
#	Mar 21, 1999: - make $sigmaR optional in &sVolAnom()
#	Mar 31, 1999: - alias &potemp() = &theta(); &podens() = &sigma()
#				  - added &temp() to calc in-situ from potemp
#	Sep 18, 1999: - parameter typechecking
#	Aug 28, 2000: - added PARAM T68 check
#	Aug 29, 2000: - forced temp_scale param check --- set to T68 or T90
#	Sep 25, 2000: - changed temp_scale to 68 or 90 (easier check)
#				  - check for temp_scale during loading
#	Nov 07, 2000: - added &dynHt()
#				  - strictified
#	Nov 13, 2000: - removed temp_scale check during loading (STUPID,
#					because PARAMs are only available after header
#					is read)
#	Feb 28, 2001: - added &rho(S,T,P)
#	Mar 28, 2001: - changed Rrho() to use podens
#				  - optimized &theta() for P == Pref
#	Apr  2, 2001: - &TurnerAngle() disabled pending adaptation to new Rrho()
#	Apr  3, 2001: - updated &TurnerAngle()
#	Jul 17, 2001: - cosmetics
#				  - added &grav(), &potErgAnom()
#	Jul 18, 2001: - cosmetics
#	Jul 20, 2001: - changed temp_scale to ITS
#	Nov 26, 2001: - added &g(), &f()
#	Dec 26, 2005: - update notes
#   Jul  1, 2006: - Version 3.3 [HISTORY]
#				  - nan must be quoted for use strict
#	Nov  5, 2006: - added K15toSalin
#	Oct 18, 2007: - adapted to new &antsFunUsage()
#				  - changed order of &salin() params
#	Oct 19, 2007: - continued
#				  - removed &grav(), podens(), potemp()
#	Dec  1, 2007: - made theta(), sigma(), rho() return nan on nan input
#	Dec 21, 2007: - BUG: grav() was still used
#	Jan 20, 2008: - BUG: theta(), sigma(), rho() still generated error on nan in
#				  - made depth(), dynht(), potErgAnom() return nan on nan in
#	Jan  4, 2011: - maded salin(), sVel() return nan on nan in
#	Oct  6, 2011: - added %cond.unit (analogous to %ITS)

require "$ANTS/libvec.pl";
use strict;

#======================================================================
# PART I: stuff taken from PEXEC
#======================================================================

{ # BEGIN STATIC SCOPE

	my($TCONV);

	sub TCONV()
	{
		unless (defined($TCONV)) {
			my($ITS) = &antsRequireParam('ITS');
			if ($ITS == 68) {
				$TCONV = 1;
			} elsif ($ITS == 90) {
				$TCONV = 1.00024;
			} else {
				croak("$0: illegal PARAM-value ITS=$ITS\n");
	        }
	    }
	    return $TCONV;
	}

} # END STATIC SCOPE

#---------------------------------------------------------------------- 
#	ADIABATIC TEMPERATURE GRADIENT DEG C/BAR							 
#	REF: BRYDEN,H.,1973,DEEP-SEA RES.,20,401-408						 
#	CHECK VALUE: ATGR80=3.255976E-3 FOR S=40 NSU,T=40 DEG C,			 
#	PIN=10000 DECIBARS
#	NB: real check value appears to be 3.255976E-4; this makes sense
#	    as check value of potential temperature below is correct. Also
#       note the 0.1 factor on return.
#---------------------------------------------------------------------- 

{ my(@fc);
	sub adiaTempGrad(@)
	{
		my($S,$T,$P) = &antsFunUsage(3,'fff','[salin, temp, press(db)]',
									 \@fc,'salin','temp','press',@_);
		my($DS);
		my($TCONV) = &TCONV();
	
		$T *= $TCONV;		# use T68 
		$P *= 0.1;
		$DS = $S - 35.0;
		return 0.1 * ((((-2.1687E-13*$T+1.8676E-11)*$T-4.6206E-10)*$P
					+((2.7759E-10*$T-1.1351E-8)*$DS+((-5.4481E-12*$T
					+8.733E-10)*$T-6.7795E-8)*$T+1.8741E-6))*$P
					+(-4.2393E-7*$T+1.8932E-5)*$DS
					+((6.6228E-9*$T-6.836E-7)*$T+8.5258E-5)*$T+3.5803E-4) / $TCONV;
	}
}

#---------------------------------------------------------------------- 
# TO COMPUTE LOCAL POTENTIAL TEMPERATURE AT PR							 
#	USING BRYDEN 1973 POLYNOMIAL FOR ADIABATIC LAPSE RATE				 
#	AND RUNGE-KUTTA 4-TH ORDER INTEGRATION ALGORITHM.					 
#	REF: BRYDEN,H.,1973,DEEP-SEA RES.,20,401-408						 
#	FOFONOFF,N.,1977,DEEP-SEA RES.,24,489-491							 
#	CHECK VALUE: PTMP83 =36.89072 FOR S=40 NSU,T=40 DEG C,				 
#	P(MEASURED PRESSURE)=10000 DECIBARS,Pref=0 DECIBARS
#---------------------------------------------------------------------- 

{ my(@fc);
	sub theta(@)
	{
		my($S,$T,$P,$Pref) =
			&antsFunUsage(4,'....','[salin, temp, press(db),] refpress(db)',
						  \@fc,'salin','temp','press',undef,@_);
		return 'nan' unless numberp($S) && numberp($T) && numberp($P);
		my($H,$XK,$Q);
		my($TCONV) = &TCONV();
	
		return $T if ($P == $Pref);
		$T *= $TCONV;	# use T68 
		$H = $Pref - $P;
		$XK = $H * &adiaTempGrad($S,$T/$TCONV,$P)*$TCONV;
		$T += 0.5 * $XK;
		$Q = $XK;
		$P += 0.5 * $H;
		$XK = $H * &adiaTempGrad($S,$T/$TCONV,$P)*$TCONV;
		$T += 0.29289322 * ($XK-$Q);
		$Q = 0.58578644*$XK + 0.121320344*$Q;
		$XK = $H * &adiaTempGrad($S,$T/$TCONV,$P)*$TCONV;
		$T += 1.707106781 * ($XK-$Q);
		$Q = 3.414213562*$XK - 4.121320344*$Q;
		$P += 0.5*$H;
		$XK = $H * &adiaTempGrad($S,$T/$TCONV,$P)*$TCONV;
		return ($T + ($XK-2.0*$Q)/6.0)/$TCONV;
	}
}

# can't easily do default fields because theta field name is not unique
sub temp(@)
{
	my($S,$T,$P,$Pref) =
		&antsFunUsage(4,"ffff","salin, potemp, press(db), refpress(db)",@_);
	return &theta($S,$T,$Pref,$P);
}

#---------------------------------------------------------------------- 
#	Calculation of specific volume anomaly
#		Gill (1982), p.215: - specific volume = inverse of density
#							- SVA referenced to same press, T=0, S=35
#																		 
#	 *************************************************					 
#	 *** USES EQUATION OF STATE FOR SEA WATER 1980 ***					 
#	 *************************************************					 
#																		 
#	Check value. SVAN83=981.30210E-8, SIGMA=59.82037					 
#	for S=40, T=40, P=10000												 
#																		 
# 	NB: - this one was a bitch to translate, used f2c and it shows :-)	 
#		- the 1e-8 factor in the check value is wrong; yup: see gill p.215
#		  (typical units are 1e-8m^3kg^-1)
#		- check val appears to be slightly off, I get 981.30187319561
#	  
#---------------------------------------------------------------------- 

sub sVolAnom(@)
{
	my($S,$T,$P,$sigmaR) =
		&antsFunUsage(-3,'fff','salin, temp, press(db)[, ref to sigma var]',@_);
	my($r3500) = 1028.1063;
	my($r4)	   = 4.8314e-4;
	my($dr350) = 28.106331;

	my($temp0,$temp1,$temp2,$temp3,$temp4);
	my($dvan,$dr35p,$p,$t,$dk,$pk,$sr,$gam,$sig,$rk35,$sva,$v350p);

	my($TCONV) = &TCONV();

	$t = $T*$TCONV;								# use T68 
	$p = $P / 10.;
#	$sr = sqrt(abs($S));
	$sr = sqrt($S);
	$temp3 = (((($t * 6.536332e-9 - 1.120083e-6) * $t + 
	  1.001685e-4) * $t - .00909529) * $t + .06793952) * $t 
	  - 28.263737;
	$temp2 = ((($t * 5.3875e-9 - 8.2467e-7) * $t + 7.6438e-5) 
	  * $t - .0040899) * $t + .824493;
	$temp1 = ($t * -1.6546e-6 + 1.0227e-4) * $t - .00572466;
	$sig = ($r4 * $S + $temp1 * $sr + $temp2) * $S + $temp3;
	$v350p = 1. / $r3500;
	$sva = -$sig * $v350p / ($r3500 + $sig);
	$$sigmaR = $sig + $dr350 if (defined($sigmaR));
	return $sva * 1e8 if ($p == 0.0);

	$temp0 = ($t * 9.1697e-10 + 2.0816e-8) * $t - 9.9348e-7;
	$temp1 = ($t * 5.2787e-8 - 6.12293e-6) * $t + 3.47718e-5;
	$temp1 = $temp1 + $temp0 * $S;
	$temp0 = 1.91075e-4;
	$temp2 = ($t * -1.6078e-6 - 1.0981e-5) * $t + .0022838;
	$temp3 = (($t * -5.77905e-7 + 1.16092e-4) * $t + .00143713) * $t - .1194975;
	$temp3 = ($temp0 * $sr + $temp2) * $S + $temp3;
	$temp0 = ($t * -5.3009e-4 + .016483) * $t + .07944;
	$temp2 = (($t * -6.167e-5 + .0109987) * $t - .603459) * $t + 54.6746;
	$temp4 = ((($t * -5.155288e-5 + .01360477) * $t - 2.327105) * $t + 148.4206) * $t - 1930.06;
	$temp4 = ($temp0 * $sr + $temp2) * $S + $temp4;
	$dk = ($temp1 * $p + $temp3) * $p + $temp4;
	$rk35 = ($p * 5.03217e-5 + 3.359406) * $p + 21582.27;
	$gam = $p / $rk35;
	$pk = 1. - $gam;
	$sva = $sva * $pk + ($v350p + $sva) * $p * $dk / ($rk35 * ($rk35 + $dk));
	$v350p *= $pk;
	$dr35p = $gam / $v350p;
	$dvan = $sva / ($v350p * ($v350p + $sva));
	$$sigmaR = $dr350 + $dr35p - $dvan if (defined($sigmaR));
	return $sva * 1e8;
}

#----------------------------------------------------------------------
# Dynamic Height (from dyht83.F)
#	Usage:
#		- dynHt(salin,temp,press[,idx])
#	Check Values from PEXEC:
#		- dynHt(40,18,20)  -> -.01879
#		- dynHt(??,16,50)  -> -.03194
#		- dynHt(??,14,100) -> -.00255
#	NB: - check values 2 & 3 do not appear to check out
#		- checked against WHOI-supplied BBTRE data indicates max
#		  diff of 1e-3 dyn.m at 5141m
#		- use different idx to use multiple times in single program
#		  (c.f. [gshear])
#		- behaves the same as the pexec version with PFIRST=0
#		- dynamic height seems to be defined (1e-5 factor) to allow
#		  station distance to be in km and velocities in cm/s
#----------------------------------------------------------------------

{ # BEGIN static scope

	my(@lastP,@lastAnom,@ht);

	sub dynHt(@)
	{
		my($S,$T,$P,$idx) =
			&antsFunUsage(-3,"...","salin, temp, press(db)[, <idx>]",@_);
		return 'nan' unless numbersp($S,$T,$P);
		my($anom) = sVolAnom($S,$T,$P) * 1e-5;

		if (!defined($ht[$idx])) {					# first call
			$ht[$idx] = $P * $anom;
		} else {									# successive calls
		    croak("$0: pressure not increasing monotonically ($lastP[$idx] -> $P)\n")
		    	if ($P < $lastP[$idx]);
			$ht[$idx] += ($P-$lastP[$idx]) * ($anom+$lastAnom[$idx])/2;
		}
		$lastP[$idx] = $P; $lastAnom[$idx] = $anom;

		return $ht[$idx];
	}

} # END static scope

#----------------------------------------------------------------------
# Local Gravity (from grav83.F)
#	Usage:
#		- g(press,lat)
#	Check Values:
#		- g(10000,30) = 9.804160
#----------------------------------------------------------------------

{ my(@fc);
	sub g(@)
	{
		my($P,$lat) = &antsFunUsage(2,'ff','[press(db), lat]',\@fc,'press','%lat',@_);
	
		my($x) = sin($lat/57.29578) ** 2;				    
		my($g) = 9.780318*(1.0+(5.2788e-3+2.36e-5*$x)*$x);	# global variation
		return $g + 1.092e-6 * $P;							# pressure correction
	}
}

#----------------------------------------------------------------------
# Coriolis frequency
#	Usage:
#		- f(%lat)
#----------------------------------------------------------------------

{ my(@fc);
	sub f(@)
	{
		my($lat) = &antsFunUsage(1,'f','[lat]',\@fc,'%lat',@_);
		my($Omega) = 7.292e-5;								# Gill (1982)
		return 2 * $Omega * sin(rad($lat));
	}
}

#----------------------------------------------------------------------
# Potential Energy Anomaly (from pean83.F)
#	Usage:
#		- potErgAnom(salin,temp,press,refPress,lat)
#	Check Values from PEXEC: (NB: sequence of calls)
#		- potErgAnom(40,18, 20,0,49.2235) = -1.91462
#		- potErgAnom(38,16, 50,0,49.2235) = -4.31092
#		- potErgAnom(36,14,100,0,49.2235) = 24.82922
#	Units:
#		- Gill (1982) p.45: m^2/s^2 = J/kg
#	NB:
#		- geopotential Phi is PEA wrt zero pressure
#		- PE of volume is volume integral of rho*PEA (c.f. Gill, p.80)
#		- check value lat calculated so that g(0,lat) ~ 9.81
#		- IF Pref<P ON 1ST CALL, HEIGHT WILL BE CALCULATED FROM LEVEL
#		  Pref.  HENCE IF Pref = 0.0, FROM SEA SURFACE DOWN.
#		- different to pexec, this version sets values for P<Pref to nan
#----------------------------------------------------------------------

{ # BEGIN STATIC SCOPE

	my($gzero,$H,$lastZ,$lastP);

	sub potErgAnom(@)
	{
		my($S,$T,$P,$Pref,$lat) =
	        &antsFunUsage(5,".....","salin, temp, press(db), refpress(db), lat",@_);
	    return 'nan' unless numbersp($S,$T,$P,$Pref,$lat);

		return 'nan' if ($P < $Pref);

		$gzero = g(0,$lat) unless (defined($gzero));		# 1st time
		my($g) = $gzero + 1.113e-4*$P;

		my($anom) = sVolAnom($S,$T,$P) * 1e-3;
		my($Z) = $anom * $P/$g;

		if (defined($H)) {									# not 1st time
		    croak("$0: pressure not increasing monotonically\n")
		    	if ($P < $lastP);
			$H += ($Z+$lastZ) * ($P-$lastP)*0.5;
		} else {
			$H = (($anom*$Pref/$g)+$Z)*($P-$Pref)*0.5;
        }
        
		$lastP = $P; $lastZ = $Z;
		return $H;
    }

} # END static scope

#---------------------------------------------------------------------- 
# Density at pressure P (P - Measured pressure)						 
# 						(PREF - Reference pressure)					 
# Equation of state for seawater proposed by JPOTS 1980				 
# References															 
#		Millero et al 1980, Deep Sea Res.,27A,255-264					 
#		Jpots Ninth Report 1978, Tenth Report 1980						 
# Units:																 
#	   Pressure   P  Decibars									 
#	   Temperature T  Deg Celcius (IPTS-68)						 
#	   Salinity   S  NSU (IPSS-78)								 
#	   Density   RHO KG/M**3										 
#	   Spec. Vol.  EOS80 M**3/KG									 
#	check value. 43.331642 for P=10000,PREF=5000,T=40,S=40				 
# NB: check value appears to be 42.33164!!!							 
#---------------------------------------------------------------------- 

{ my(@fc); 
	sub sigma(@)
	{
		my($S,$T,$P,$Pref) =
			&antsFunUsage(4,'....','[salin, temp, press(db),] refpress(db)',
						  \@fc,'salin','temp','press',undef,@_);
		return 'nan' unless numberp($S) && numberp($T) && numberp($P);
		my($sig);
		&sVolAnom($S,&theta($S,$T,$P,$Pref),$Pref,\$sig);
		return $sig;
	}
}

{ my(@fc);
	sub rho(@)
	{
		my($S,$T,$P) = &antsFunUsage(3,'...','[salin, temp, press(db)]',
									 \@fc,'salin','temp','press',@_);
		return 'nan' unless numberp($S) && numberp($T) && numberp($P);
		return 1000 + &sigma($S,$T,$P,$P);
	}
}

#---------------------------------------------------------------------- 
#	FUNCTION TO CONVERT CONDUCTIVITY TO SALINITY ACCORDING TO THE		 
#	ALGORITHMS RECOMMMENDED BY JPOTS USING THE 1978 PRACTICAL 			 
#	SALINITY SCALE (IPSS-78) AND IPTS-68 FOR TEMPERATURE. 				 
#	C1535=CONDUCTIVITY (FROM CULKIN&SMITH,1980,J.OCEAN.ENG.VOL.5 		 
#    PP22-23) = 42.9140 AT    PRES=0.0, 							 
#	SALINITY 35 NSU AND TEMPERATURE 15 DEG CELSIUS (IPTS-68) 			 
#	PRESSURE=DECIBARS 													 
#	RETURNS ZERO FOR CND<.0005 		 									 
#	CHECKVALUES.... 													 
#	SAL83 =40.000000 FOR CND=81.025545,T=40 DEG C,PIN=10000 			 
#   DECIBARS														 
#	WRITTEN BY N FOFONOFF; REVISED OCT 6 1980 							 
#	FCN SAL83, XR=SQRT(RT) 												 
#	DERIVATIVE WRT XR ; DSAL/DXR 										 
#	RT35 																 
#	C,B,A, POLYNOMIALS 													 
#	NB: - done using f2c (couldn't be bothered)							 
#		- removed SAL->COND												 
#---------------------------------------------------------------------- 

{ my(@fc);
  my($cond_scale);	# 1 for mS/cm, 10 for S/m
	sub salin(@)
	{
		my($C,$T,$P) = &antsFunUsage(3,'...','[cond, temp, press(db)]',
									 \@fc,'cond','temp','press',@_);
		return 'nan' unless numberp($C) && numberp($T) && numberp($P);
		my($r__,$dt,$rt,$c1535);
		my($TCONV) = &TCONV();

		unless (defined($cond_scale)) {				# deal with different conductivity units
			my($cu) = &antsRequireParam('cond.unit');
			if    ($cu eq 'S/m')   { $cond_scale = 10; }
			elsif ($cu eq 'mS/cm') { $cond_scale = 1;  }
			else { croak("$0: illegal PARAM-value cond.unit=$cu\n"); }
		}
		$C *= $cond_scale;
	
		return 0.0 if ($C <= 5e-4); 				# zero salinity trap 
		$T *= $TCONV;								# use T68 scale 
		$c1535 = 42.914;
		$dt = $T - 15.0;
		$P *= .1;									# convert pressure to bars 
		$r__ = $C / $c1535; 						# convert cond to salin
		$rt = $r__ / ((((($T * 1.0031e-9 - 6.9698e-7) * $T + 
		  1.104259e-4) * $T + .0200564) * $T + .6766097) * ((($P 
		  * 3.989e-12 - 6.37e-8) * $P + 2.07e-4) * $P / (
		  ($T * 4.464e-4 + .03426) * $T + 1. + ($T *
		  -.003107 + .4215) * $r__) + 1.));
		$rt = sqrt(abs($rt));
		return (((($rt * 2.7081 - 7.0261) * $rt + 14.0941) *
		   $rt + 25.3851) * $rt - .1692) * $rt + .008 + 
		  $dt / ($dt * .0162 + 1.) * ((((($rt * -.0144 + 
		  .0636) * $rt - .0375) * $rt - .0066) * $rt - 
		  .0056) * $rt + 5e-4);
	}
}

#----------------------------------------------------------------------
# K15toSalin; see http://ioc.unesco.org/oceanteacher/OceanTeacher2/01_GlobOcToday/02_CollDta/02_OcDtaFunda/03_T&SScales/TemperatureAndSalinityScales.htm
#----------------------------------------------------------------------

sub K15toSalin(@)
{
	my($K15) = &antsFunUsage(1,'f','K15',@_);
	return  0.0080 -  0.1692 * $K15**0.5
				   + 25.3851 * $K15**1.0
				   + 14.0941 * $K15**1.5
				   -  7.0261 * $K15**2.0
				   +  2.7081 * $K15**2.5;
}

#---------------------------------------------------------------------- 
#	DEPTH IN METERS FROM PRESSURE IN DECIBARS USING						 
#	SAUNDERS AND FOFONOFF'S METHOD.										 
#	DEEP SEA RES., 1976,23,109-111.										 
#	FORMULA REFITTED FOR EOS80											 
#	CHECK VALUE. 9712.654 M FOR PIN=10000 DECIBARS,LATITUDE=30 DEG		 
#	..CONVERT PRESSURE TO BARS
#	NB: results are closer to Saunders, JPO 11, 1981 than ref given
#	    above. They are not exact, either, however.
#---------------------------------------------------------------------- 

{ my(@fc);
	sub depth(@)
	{
		my($P,$lat) = &antsFunUsage(2,'..','[press(db), lat]',\@fc,'press','%lat',@_);
		return 'nan' unless numbersp($P,$lat);
		my($x) = sin($lat/57.29578);
		$P *= 0.1;
		$x = $x*$x;
		return ((((-1.82E-11*$P+2.279E-7)*$P-2.2512E-3)*$P+97.2659)*$P) /
			  (9.780318*(1.0+(5.2788E-3+2.36E-5*$x)*$x) + 1.092E-5*$P);
	}
}

#----------------------------------------------------------------------
#	Convert depth to pressure using 2nd order polynomial
#	From file pdepth.F (pexec)
#	This is based on Saunders, JPO 11, 1981
#----------------------------------------------------------------------

{ my(@fc);
	sub press(@)
	{
		my($d,$lat) = &antsFunUsage(2,'ff','[depth, lat]',\@fc,'depth','%lat',@_);
		my($c1) = 5.92E-3 + 5.25E-3 * sin($lat/57.29578) ** 2;
		my($c2) = 2.21E-6;
		my($press) = (1 - $c1 - sqrt((1 - $c1)**2 - 4*$d*$c2)) / (2*$c2);
		my($derr) = abs(&depth($press,$lat) - $d);
	    
		&antsInfo("WARNING (libEOS83.pl): %.1gm depth error due to pressure " .
				  "approximation", $derr) if ($derr >= 1);
		return $press;
	}
}

#---------------------------------------------------------------------- 
#	SOUND SPEED SEAWATER (CHEN & MILLERO 1977, JASA,62,1129-1135) 		 
#	SPEED IN M/S, P IN DECIBARS, T DEG C (IPTS-68), S NSU(IPSS-78) 		 
#	CHECK VALUE : 1731.9954 M/S FOR PIN=10000, T=40, C,S=40 			 
#	NB: - f2c used (because of fortran `equivalence')					 
#---------------------------------------------------------------------- 

{ my(@fc);
	sub sVel(@)
	{
		my($S,$T,$P) = &antsFunUsage(3,'...','[salin, temp, press(db)]',
									 \@fc,'salin','temp','press',@_);
		return 'nan' unless numberp($S) && numberp($T) && numberp($P);
		my($temp0,$temp1,$temp2,$temp3);
		my($a,$b,$c__,$d__,$sr);
		my($TCONV) = &TCONV();
	
		$T *= $TCONV;									# T68 
		$P *= .1;										# CONVERT PRESSURE TO BARS 
		$sr = sqrt(abs($S));							# S**2 TERM 
		$d__ = .001727 - $P * 7.9836e-6;
		$temp1 = $T * 1.7945e-7 + 7.3637e-5;			# S**3/2 TERM 
		$temp0 = -.01922 - $T * 4.42e-5;
		$b = $temp0 + $temp1 * $P;
		$temp3 = ($T * -3.389e-13 + 6.649e-12) * $T + 1.1e-10;	# S**1 TERM 
		$temp2 = (($T * 7.988e-12 - 1.6002e-10) * $T + 9.1041e-9) * $T - 3.9064e-7;
		$temp1 = ((($T * -2.0122e-10 + 1.0507e-8) * $T - 6.4885e-8) * $T
			- 1.258e-5) * $T + 9.4742e-5;
		$temp0 = ((($T * -3.21e-8 + 2.006e-6) * $T + 7.164e-5) * $T - .01262)
			* $T + 1.389;
		$a = (($temp3 * $P + $temp2) * $P + $temp1) * $P + $temp0;
		$temp3 = ($T * -2.3643e-12 + 3.8504e-10) * $T - 9.7729e-9;	# S**0 TERM 
		$temp2 = ((($T * 1.0405e-12 - 2.5335e-10) * $T + 2.5974e-8) * $T
			- 1.7107e-6) * $T + 3.126e-5;
		$temp1 = ((($T * -6.1185e-10 + 1.3621e-7) * $T - 8.1788e-6) * $T
			+ 6.8982e-4) * $T + .153563;
		$temp0 = (((($T * 3.1464e-9 - 1.478e-6) * $T + 3.342e-4) * $T - .0580852)
			* $T + 5.03711) * $T + 1402.388;
		$c__ = (($temp3 * $P + $temp2) * $P + $temp1) * $P + $temp0;
		return $c__ + ($a + $b * $sr + $d__ * $S) * $S; 		# SOUND SPEED RETURN 
	}
}

#======================================================================
# PART II: Homegrown Stuff
#======================================================================

#----------------------------------------------------------------------
# &alpha(S,T,P)		linear thermal expansion coefficient
# 	Notes:			- use temperature interval of 0.2 degrees
#					- depth instead of pressure ok
# 	Check Value:	- alpha(35,2,6000) = 0.000209447279306853
#----------------------------------------------------------------------

sub alpha(@)
{
	my($S,$T,$P) = &antsFunUsage(3,"fff","salin, temp, press | depth",@_);
	return (&sigma($S,$T-.1,$P,$P) - &sigma($S,$T+.1,$P,$P))
			/ (.2 * (1000+&sigma($S,$T,$P,$P)));
}

#----------------------------------------------------------------------
# &beta(S,T,P)		linear haline contraction coefficient
# 	Notes:			- use salinity interval of 0.02 psu
#					- depth instead of pressure ok
# 	Check Value:	- beta(35,2,6000) = 0.000718513652714652 (should check Gill)
#----------------------------------------------------------------------

sub beta(@)
{
	my($S,$T,$P) = &antsFunUsage(3,"fff","salin, temp, press | depth",@_);
	return (&sigma($S+0.01,$T,$P,$P) - &sigma($S-0.01,$T,$P,$P))
			/ (.02 * (1000+&sigma($S,$T,$P,$P)));
}

#----------------------------------------------------------------------
# &Rrho(midS,S0,S1,midT,T0,T1,midP,P0,P1)	stability ratio
# 	Notes:			- depth instead of pressure ok
# 	Check Value:	- Rrho(35.05,35.1,35,4.27,4.82,3.72,2100,2100,2100) = 2.222
#----------------------------------------------------------------------

sub alphaDT(@)
{
	my($S,$T0,$T1,$P,$P0,$P1) = @_;
#		&antsFunUsage(6,"ffffff","S, T0, T1, P, P0, P1",@_);
	my($sgn) = ($P1 > $P0) ? 1 : -1;
	return $sgn * (&sigma($S,$T1,$P1,$P) - &sigma($S,$T0,$P0,$P));
}

sub betaDS(@)
{
	my($S0,$S1,$T,$P,$P0,$P1) = @_;
#		&antsFunUsage(6,"ffffff","S0, S1, T, P, P0, P1",@_);
	my($sgn) = ($P1 > $P0) ? 1 : -1;
	return $sgn * (&sigma($S0,$T,$P0,$P) - &sigma($S1,$T,$P1,$P));
}

sub Rrho(@)
{
	my($S,$S0,$S1,$T,$T0,$T1,$P,$P0,$P1) =
		&antsFunUsage(9,"fffffffff","S, S0, S1, T, T0, T1, P, P0, P1",@_);
	my($aDT) = &alphaDT($S,$T0,$T1,$P,$P0,$P1);
	my($bDS) = &betaDS($S0,$S1,$T,$P,$P0,$P1);
	return $bDS == 0 ? 'nan' : $aDT / $bDS;
}

#----------------------------------------------------------------------
# &TurnerAngle(midS,S0,S1,midT,T0,T1,midP,P0,P1)	Turner Angle
# 	Notes:						- c.f. &Rrho()
#								- -45<Tu<45 	doubly stable
#								- Tu<-90, Tu>90 statically unstable
#								- 45<Tu<90 		fingering regime
#								- -90<Tu<-45	diffusive regime
# 	Check Value: TurnerAngle(35.05,35.1,35,4.27,4.82,3.72,2100,2100,2100.01) = 69.2296333539803
#----------------------------------------------------------------------

sub TurnerAngle(@)
{
	my($S,$S0,$S1,$T,$T0,$T1,$P,$P0,$P1) =
		&antsFunUsage(9,"fffffffff","S, S0, S1, T, T0, T1, P, P0, P1",@_);
	my($aDT) = &alphaDT($S,$T0,$T1,$P,$P0,$P1);
	my($bDS) = &betaDS($S0,$S1,$T,$P,$P0,$P1);
	return atan2($aDT+$bDS,$aDT-$bDS)*57.29577951;
}

1;