← back to index

src/equation_of_state/MOM_EOS.F90

portedportable, not yet portedexecuted, not portableexecutable, not hit by this run

1! This file is part of MOM6, the Modular Ocean Model version 6.
2! See the LICENSE file for licensing information.
3! SPDX-License-Identifier: Apache-2.0
4
5!> Provides subroutines for quantities specific to the equation of state
6module MOM_EOS
7
8use MOM_EOS_base_type, only : EOS_base
9use MOM_EOS_linear, only : linear_EOS, avg_spec_vol_linear
10use MOM_EOS_linear, only : int_density_dz_linear, int_spec_vol_dp_linear
11use MOM_EOS_Wright, only : buggy_Wright_EOS, avg_spec_vol_buggy_Wright
12use MOM_EOS_Wright, only : int_density_dz_wright, int_spec_vol_dp_wright
13use MOM_EOS_Wright_full, only : Wright_full_EOS, avg_spec_vol_Wright_full
14use MOM_EOS_Wright_full, only : int_density_dz_wright_full, int_spec_vol_dp_wright_full
15use MOM_EOS_Wright_red, only : Wright_red_EOS, avg_spec_vol_Wright_red
16use MOM_EOS_Wright_red, only : int_density_dz_wright_red, int_spec_vol_dp_wright_red
17use MOM_EOS_Jackett06, only : Jackett06_EOS
18use MOM_EOS_UNESCO, only : UNESCO_EOS
19use MOM_EOS_Roquet_rho, only : Roquet_rho_EOS
20use MOM_EOS_Roquet_SpV, only : Roquet_SpV_EOS
21use MOM_EOS_TEOS10, only : TEOS10_EOS
22use MOM_EOS_TEOS10, only : gsw_sp_from_sr, gsw_pt_from_ct, gsw_sr_from_sp, gsw_ct_from_pt
23use MOM_temperature_convert, only : poTemp_to_consTemp, consTemp_to_poTemp
24use MOM_TFreeze, only : calculate_TFreeze_linear, calculate_TFreeze_Millero
25use MOM_TFreeze, only : calculate_TFreeze_teos10, calculate_TFreeze_TEOS_poly
26use MOM_error_handler, only : MOM_error, FATAL, WARNING, MOM_mesg
27use MOM_file_parser, only : get_param, log_version, param_file_type
28use MOM_hor_index, only : hor_index_type
29use MOM_io, only : stdout, stderr
30use MOM_string_functions, only : uppercase
31use MOM_unit_scaling, only : unit_scale_type
32
33implicit none ; private
34
35public EOS_domain
36public EOS_init
37public EOS_manual_init
38public EOS_quadrature
39! public EOS_use_linear
40public EOS_fit_range
41public EOS_unit_tests
42public analytic_int_density_dz
43public analytic_int_specific_vol_dp
44public average_specific_vol
45public calculate_compress
46public calculate_density_elem
47public calculate_density
48public calculate_density_derivs
49public calculate_density_second_derivs
50public calculate_spec_vol
51public calculate_specific_vol_derivs
52public calculate_TFreeze
53public convert_temp_salt_for_TEOS10
54public cons_temp_to_pot_temp
55public pot_temp_to_cons_temp
56public abs_saln_to_prac_saln
57public prac_saln_to_abs_saln
58public gsw_sp_from_sr
59public gsw_sr_from_sp
60public gsw_pt_from_ct
61public query_compressible
62public get_EOS_name
63
64! A note on unit descriptions in comments: MOM6 uses units that can be rescaled for dimensional
65! consistency testing. These are noted in comments with units like Z, H, L, and T, along with
66! their mks counterparts with notation like "a velocity [Z T-1 ~> m s-1]". If the units
67! vary with the Boussinesq approximation, the Boussinesq variant is given first.
68
69!> Calculates density of sea water from T, S and P
70interface calculate_density
71 module procedure calculate_density_scalar
72 module procedure calculate_density_1d
73 module procedure calculate_density_2d
74 module procedure calculate_density_3d
75 module procedure calculate_stanley_density_scalar
76 module procedure calculate_stanley_density_1d
77 module procedure calculate_stanley_density_2d
78end interface calculate_density
79
80!> Calculates specific volume of sea water from T, S and P
81interface calculate_spec_vol
82 module procedure calc_spec_vol_scalar
83 module procedure calc_spec_vol_1d
84end interface calculate_spec_vol
85
86!> Calculate the derivatives of density with temperature and salinity from T, S, and P
87interface calculate_density_derivs
88 module procedure calculate_density_derivs_scalar, calculate_density_derivs_array
89 module procedure calculate_density_derivs_1d
90 module procedure calculate_density_derivs_2d
91 module procedure calculate_density_derivs_3d
92end interface calculate_density_derivs
93
94!> Calculate the derivatives of specific volume with temperature and salinity from T, S, and P
95interface calculate_specific_vol_derivs
96 module procedure calc_spec_vol_derivs_1d
97end interface calculate_specific_vol_derivs
98
99!> Calculates the second derivatives of density with various combinations of temperature,
100!! salinity, and pressure from T, S and P
101interface calculate_density_second_derivs
102 module procedure calculate_density_second_derivs_scalar, calculate_density_second_derivs_1d
103 module procedure calculate_density_second_derivs_2d
104end interface calculate_density_second_derivs
105
106!> Calculates the freezing point of sea water from T, S and P
107interface calculate_TFreeze
108 module procedure calculate_TFreeze_scalar, calculate_TFreeze_1d, calculate_TFreeze_array
109end interface calculate_TFreeze
110
111!> Calculates the compressibility of water from T, S, and P
112interface calculate_compress
113 module procedure calculate_compress_scalar, calculate_compress_1d
114end interface calculate_compress
115
116!> A control structure for the equation of state
117type, public :: EOS_type ; private
118 integer :: form_of_EOS = 0 !< The equation of state to use.
119 integer :: form_of_TFreeze = 0 !< The expression for the potential temperature
120 !! of the freezing point.
121 logical :: EOS_quadrature !< If true, always use the generic (quadrature)
122 !! code for the integrals of density.
123 logical :: Compressible = .true. !< If true, in situ density is a function of pressure.
124! The following parameters are used with the linear equation of state only.
125 real :: Rho_T0_S0 !< The density at T=0, S=0 [kg m-3]
126 real :: dRho_dT !< The partial derivative of density with temperature [kg m-3 degC-1]
127 real :: dRho_dS !< The partial derivative of density with salinity [kg m-3 ppt-1]
128 real :: dRho_dp !< The partial derivative of density with pressure [s2 m-2]
129! The following parameters are use with the linear expression for the freezing
130! point only.
131 real :: TFr_S0_P0 !< The freezing potential temperature at S=0, P=0 [degC]
132 real :: dTFr_dS !< The derivative of freezing point with salinity [degC ppt-1]
133 real :: dTFr_dp !< The derivative of freezing point with pressure [degC Pa-1]
134! The following are logicals pertaining to definitions of the thermodynamic state variables
135 logical :: use_conT_absS =.false. !< True if the model internal temperature is the conservative temperature and
136 !! the salinity is absolute salinity. These could be separated into two flags,
137 !! but right now it is controlled by one input parameter and there is no known
138 !! need to have one True and one False.
139 logical :: TFreeze_S_is_pracS =.true. !< True if the freezing point expression is formulated from practical salinity
140 logical :: TFreeze_T_is_potT = .true. !< True if the freezing point expression yields a potential temperature
141
142 logical :: use_Wright_2nd_deriv_bug = .false. !< If true, use a separate subroutine that
143 !! retains a buggy version of the calculations of the second
144 !! derivative of density with temperature and with temperature and
145 !! pressure. This bug is corrected in the default version.
146
147! Unit conversion factors (normally used for dimensional testing but could also allow for
148! change of units of arguments to functions)
149 real :: m_to_Z = 1. !< A constant that translates distances in meters to the units of depth [Z m-1 ~> 1]
150 real :: kg_m3_to_R = 1. !< A constant that translates kilograms per meter cubed to the
151 !! units of density [R m3 kg-1 ~> 1]
152 real :: R_to_kg_m3 = 1. !< A constant that translates the units of density to
153 !! kilograms per meter cubed [kg m-3 R-1 ~> 1]
154 real :: RL2_T2_to_Pa = 1.!< Convert pressures from R L2 T-2 to Pa [Pa T2 R-1 L-2 ~> 1]
155 real :: L_T_to_m_s = 1. !< Convert lateral velocities from L T-1 to m s-1 [m T s-1 L-1 ~> 1]
156 real :: degC_to_C = 1. !< A constant that translates degrees Celsius to the units of temperature [C degC-1 ~> 1]
157 real :: C_to_degC = 1. !< A constant that translates the units of temperature to degrees Celsius [degC C-1 ~> 1]
158 real :: ppt_to_S = 1. !< A constant that translates parts per thousand to the units of salinity [S ppt-1 ~> 1]
159 real :: S_to_ppt = 1. !< A constant that translates the units of salinity to parts per thousand [ppt S-1 ~> 1]
160
161 !> The instance of the actual equation of state
162 class(EOS_base), allocatable :: type
163
164end type EOS_type
165
166! The named integers that might be stored in eqn_of_state_type%form_of_EOS.
167integer, parameter, public :: EOS_LINEAR = 1 !< A named integer specifying an equation of state
168integer, parameter, public :: EOS_UNESCO = 2 !< A named integer specifying an equation of state
169integer, parameter, public :: EOS_WRIGHT = 3 !< A named integer specifying an equation of state
170integer, parameter, public :: EOS_WRIGHT_FULL = 4 !< A named integer specifying an equation of state
171integer, parameter, public :: EOS_WRIGHT_REDUCED = 5 !< A named integer specifying an equation of state
172integer, parameter, public :: EOS_TEOS10 = 6 !< A named integer specifying an equation of state
173integer, parameter, public :: EOS_ROQUET_RHO = 7 !< A named integer specifying an equation of state
174integer, parameter, public :: EOS_ROQUET_SPV = 8 !< A named integer specifying an equation of state
175integer, parameter, public :: EOS_JACKETT06 = 9 !< A named integer specifying an equation of state
176!> A list of all the available EOS
177integer, dimension(9), public :: list_of_EOS = (/ EOS_LINEAR, EOS_UNESCO, &
178 EOS_WRIGHT, EOS_WRIGHT_FULL, EOS_WRIGHT_REDUCED, &
179 EOS_TEOS10, EOS_ROQUET_RHO, EOS_ROQUET_SPV, EOS_JACKETT06 /)
180
181character*(12), parameter :: EOS_LINEAR_STRING = "LINEAR" !< A string for specifying the equation of state
182character*(12), parameter :: EOS_UNESCO_STRING = "UNESCO" !< A string for specifying the equation of state
183character*(12), parameter :: EOS_JACKETT_STRING = "JACKETT_MCD" !< A string for specifying the equation of state
184character*(12), parameter :: EOS_WRIGHT_STRING = "WRIGHT" !< A string for specifying the equation of state
185character*(16), parameter :: EOS_WRIGHT_RED_STRING = "WRIGHT_REDUCED" !< A string for specifying the equation of state
186character*(12), parameter :: EOS_WRIGHT_FULL_STRING = "WRIGHT_FULL" !< A string for specifying the equation of state
187character*(12), parameter :: EOS_TEOS10_STRING = "TEOS10" !< A string for specifying the equation of state
188character*(12), parameter :: EOS_NEMO_STRING = "NEMO" !< A string for specifying the equation of state
189character*(12), parameter :: EOS_ROQUET_RHO_STRING = "ROQUET_RHO" !< A string for specifying the equation of state
190character*(12), parameter :: EOS_ROQUET_SPV_STRING = "ROQUET_SPV" !< A string for specifying the equation of state
191character*(12), parameter :: EOS_JACKETT06_STRING = "JACKETT_06" !< A string for specifying the equation of state
192character*(12), parameter :: EOS_DEFAULT = EOS_WRIGHT_FULL_STRING !< The default equation of state
193
194integer, parameter :: TFREEZE_LINEAR = 1 !< A named integer specifying a freezing point expression
195integer, parameter :: TFREEZE_MILLERO = 2 !< A named integer specifying a freezing point expression
196integer, parameter :: TFREEZE_TEOS10 = 3 !< A named integer specifying a freezing point expression
197integer, parameter :: TFREEZE_TEOSPOLY = 4 !< A named integer specifying a freezing point expression
198character*(10), parameter :: TFREEZE_LINEAR_STRING = "LINEAR" !< A string for specifying the freezing point expression
199character*(10), parameter :: TFREEZE_MILLERO_STRING = "MILLERO_78" !< A string for specifying the
200 !! freezing point expression
201character*(10), parameter :: TFREEZE_TEOSPOLY_STRING = "TEOS_POLY" !< A string for specifying the
202 !! freezing point expression
203character*(10), parameter :: TFREEZE_TEOS10_STRING = "TEOS10" !< A string for specifying the freezing point expression
204
205contains
206
207!> Density of sea water (in-situ if pressure is local) [R ~> kg m-3]
208!!
209!! If rho_ref is present, the anomaly with respect to rho_ref is returned. The pressure and
210!! density can be rescaled with the values stored in EOS. If the scale argument is present the density
211!! scaling uses the product of the two scaling factors.
2120real elemental function calculate_density_elem(EOS, T, S, pressure, rho_ref, scale)
213 type(EOS_type), intent(in) :: EOS !< Equation of state structure
214 real, intent(in) :: T !< Potential temperature referenced to the surface [C ~> degC]
215 real, intent(in) :: S !< Salinity [S ~> ppt]
216 real, intent(in) :: pressure !< Pressure [R L2 T-2 ~> Pa]
217 real, optional, intent(in) :: rho_ref !< A reference density [R ~> kg m-3]
218 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale output density in
219 !! combination with scaling stored in EOS [various]
220 real :: Ta ! An array of temperatures [degC]
221 real :: Sa ! An array of salinities [ppt]
222 real :: pres ! An mks version of the pressure to use [Pa]
223 real :: rho_mks ! An mks version of the density to be returned [kg m-3]
224 real :: rho_scale ! A factor to convert density from kg m-3 to the desired units [R m3 kg-1 ~> 1]
225
2260 pres = EOS%RL2_T2_to_Pa * pressure
2270 Ta = EOS%C_to_degC * T
2280 Sa = EOS%S_to_ppt * S
229
2300 if (present(rho_ref)) then
2310 rho_mks = EOS%type%density_anomaly_elem(Ta, Sa, pres, EOS%R_to_kg_m3*rho_ref)
232 else
2330 rho_mks = EOS%type%density_elem(Ta, Sa, pres)
234 endif
235
236 ! Rescale the output density to the desired units.
2370 rho_scale = EOS%kg_m3_to_R
2380 if (present(scale)) rho_scale = rho_scale * scale
2390 calculate_density_elem = rho_scale * rho_mks
240
2410end function calculate_density_elem
242
243!> Calls the appropriate subroutine to calculate density of sea water for scalar inputs.
244!! If rho_ref is present, the anomaly with respect to rho_ref is returned. The pressure and
245!! density can be rescaled with the values stored in EOS. If the scale argument is present the density
246!! scaling uses the product of the two scaling factors.
2470subroutine calculate_density_scalar(T, S, pressure, rho, EOS, rho_ref, scale)
248 real, intent(in) :: T !< Potential temperature referenced to the surface [C ~> degC]
249 real, intent(in) :: S !< Salinity [S ~> ppt]
250 real, intent(in) :: pressure !< Pressure [R L2 T-2 ~> Pa]
251 real, intent(out) :: rho !< Density (in-situ if pressure is local) [R ~> kg m-3]
252 type(EOS_type), intent(in) :: EOS !< Equation of state structure
253 real, optional, intent(in) :: rho_ref !< A reference density [R ~> kg m-3]
254 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale output density in
255 !! combination with scaling stored in EOS [various]
256
257 real :: Ta ! An array of temperatures [degC]
258 real :: Sa ! An array of salinities [ppt]
259 real :: pres ! An mks version of the pressure to use [Pa]
260 real :: rho_mks ! An mks version of the density to be returned [kg m-3]
261 real :: rho_scale ! A factor to convert density from kg m-3 to the desired units [R m3 kg-1 ~> 1]
262
2630 pres = EOS%RL2_T2_to_Pa * pressure
2640 Ta = EOS%C_to_degC * T
2650 Sa = EOS%S_to_ppt * S
266
2670 if (present(rho_ref)) then
2680 rho_mks = EOS%type%density_anomaly_elem(Ta, Sa, pres, EOS%R_to_kg_m3*rho_ref)
269 else
2700 rho_mks = EOS%type%density_elem(Ta, Sa, pres)
271 endif
272
273 ! Rescale the output density to the desired units.
2740 rho_scale = EOS%kg_m3_to_R
2750 if (present(scale)) rho_scale = rho_scale * scale
2760 rho = rho_scale * rho_mks
277
2780end subroutine calculate_density_scalar
279
280!> Calls the appropriate subroutine to calculate density of sea water for scalar inputs
281!! including the variance of T, S and covariance of T-S.
282!! The calculation uses only the second order correction in a series as discussed
283!! in Stanley et al., 2020.
284!! If rho_ref is present, the anomaly with respect to rho_ref is returned. The
285!! density can be rescaled using rho_ref.
2860subroutine calculate_stanley_density_scalar(T, S, pressure, Tvar, TScov, Svar, rho, EOS, rho_ref, scale)
287 real, intent(in) :: T !< Potential temperature referenced to the surface [C ~> degC]
288 real, intent(in) :: S !< Salinity [S ~> ppt]
289 real, intent(in) :: Tvar !< Variance of potential temperature referenced to the surface [C2 ~> degC2]
290 real, intent(in) :: TScov !< Covariance of potential temperature and salinity [C S ~> degC ppt]
291 real, intent(in) :: Svar !< Variance of salinity [S2 ~> ppt2]
292 real, intent(in) :: pressure !< Pressure [R L2 T-2 ~> Pa]
293 real, intent(out) :: rho !< Density (in-situ if pressure is local) [R ~> kg m-3]
294 type(EOS_type), intent(in) :: EOS !< Equation of state structure
295 real, optional, intent(in) :: rho_ref !< A reference density [R ~> kg m-3].
296 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale output density in
297 !! combination with scaling stored in EOS [various]
298 ! Local variables
299 real :: d2RdTT ! Second derivative of density with temperature [R C-2 ~> kg m-3 degC-2]
300 real :: d2RdST ! Second derivative of density with temperature and salinity [R S-1 C-1 ~> kg m-3 degC-1 ppt-1]
301 real :: d2RdSS ! Second derivative of density with salinity [R S-2 ~> kg m-3 ppt-2]
302 real :: d2RdSp ! Second derivative of density with salinity and pressure [T2 S-1 L-2 ~> kg m-3 ppt-1 Pa-1]
303 real :: d2RdTp ! Second derivative of density with temperature and pressure [T2 C-1 L-2 ~> kg m-3 degC-1 Pa-1]
304
3050 call calculate_density_scalar(T, S, pressure, rho, EOS, rho_ref)
3060 call calculate_density_second_derivs_scalar(T, S, pressure, d2RdSS, d2RdST, d2RdTT, d2RdSp, d2RdTP, EOS)
307
308 ! Equation 25 of Stanley et al., 2020.
3090 rho = rho + ( 0.5 * d2RdTT * Tvar + ( d2RdST * TScov + 0.5 * d2RdSS * Svar ) )
310
3110 if (present(scale)) rho = rho * scale
312
3130end subroutine calculate_stanley_density_scalar
314
315!> Calls the appropriate subroutine to calculate the density of sea water for 1-D array inputs,
316!! potentially limiting the domain of indices that are worked on.
317!! If rho_ref is present, the anomaly with respect to rho_ref is returned.
31844738subroutine calculate_density_1d(T, S, pressure, rho, EOS, dom, rho_ref, scale)
319 real, dimension(:), intent(in) :: T !< Potential temperature referenced to the surface [C ~> degC]
320 real, dimension(:), intent(in) :: S !< Salinity [S ~> ppt]
321 real, dimension(:), intent(in) :: pressure !< Pressure [R L2 T-2 ~> Pa]
322 real, dimension(:), intent(inout) :: rho !< Density (in-situ if pressure is local) [R ~> kg m-3]
323 type(EOS_type), intent(in) :: EOS !< Equation of state structure
324 integer, dimension(2), optional, intent(in) :: dom !< The domain of indices to work on, taking
325 !! into account that arrays start at 1.
326 real, optional, intent(in) :: rho_ref !< A reference density [R ~> kg m-3]
327 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale density
328 !! in combination with scaling stored in EOS [various]
329 ! Local variables
330 real :: rho_scale ! A factor to convert density from kg m-3 to the desired units [R m3 kg-1 ~> 1]
33189476 real, dimension(size(rho)) :: pres ! Pressure converted to [Pa]
33289476 real, dimension(size(rho)) :: Ta ! Temperature converted to [degC]
33389476 real, dimension(size(rho)) :: Sa ! Salinity converted to [ppt]
334 integer :: i, is, ie, npts
335
33644738 if (present(dom)) then
33744738 is = dom(1) ; ie = dom(2) ; npts = 1 + ie - is
338 else
3390 is = 1 ; ie = size(rho) ; npts = 1 + ie - is
340 endif
341
342 if ((EOS%RL2_T2_to_Pa == 1.0) .and. (EOS%R_to_kg_m3 == 1.0) .and. &
34344738 (EOS%C_to_degC == 1.0) .and. (EOS%S_to_ppt == 1.0)) then
34444738 call EOS%type%calculate_density_array(T, S, pressure, rho, is, npts, rho_ref=rho_ref)
345 else ! This is the same as above, but with some extra work to rescale variables.
3460 do i=is,ie
3470 pres(i) = EOS%RL2_T2_to_Pa * pressure(i)
3480 Ta(i) = EOS%C_to_degC * T(i)
3490 Sa(i) = EOS%S_to_ppt * S(i)
350 enddo
3510 if (present(rho_ref)) then
3520 call EOS%type%calculate_density_array(Ta, Sa, pres, rho, is, npts, rho_ref=EOS%R_to_kg_m3*rho_ref)
353 else
3540 call EOS%type%calculate_density_array(Ta, Sa, pres, rho, is, npts)
355 endif
356 endif
357
35844738 rho_scale = EOS%kg_m3_to_R
35944738 if (present(scale)) rho_scale = rho_scale * scale
36044738 if (rho_scale /= 1.0) then ; do i=is,ie
3610 rho(i) = rho_scale * rho(i)
362 enddo ; endif
363
36444738end subroutine calculate_density_1d
365
366
367!> 2D version...
36824subroutine calculate_density_2d(T, S, pressure, rho, EOS, dom, rho_ref)
369 real, intent(in) :: T(:,:)
370 !< Potential temperature referenced to the surface [C ~> degC]
371 real, intent(in) :: S(:,:)
372 !< Salinity [S ~> ppt]
373 real, intent(in) :: pressure(:,:)
374 !< Pressure [R L2 T-2 ~> Pa]
375 real, intent(inout) :: rho(:,:)
376 !< Density (in-situ if pressure is local) [R ~> kg m-3]
377 type(EOS_type), intent(in) :: EOS
378 !< Equation of state structure
379 integer, optional, intent(in) :: dom(2,2)
380 !< The domain of indices to work on, taking into account that arrays start
381 !! at 1.
382 real, optional, intent(in) :: rho_ref
383 !< A reference density [R ~> kg m-3]
384
38548 real, dimension(size(rho,1), size(rho,2)) :: pres
386 ! Pressure converted to [Pa]
38748 real, dimension(size(rho,1), size(rho,2)) :: Ta
388 ! Temperature converted to [degC]
38948 real, dimension(size(rho,1), size(rho,2)) :: Sa
390 ! Salinity converted to [ppt]
391 integer :: is, ie, js, je, npts
392 integer :: domain(2,2)
393
39424 if (present(dom)) then
39524 domain(:,:) = dom(:,:)
396 else
3970 domain(1,:) = [1, size(rho,1)]
3980 domain(2,:) = [1, size(rho,2)]
399 endif
400
401 if ((EOS%RL2_T2_to_Pa == 1.0) .and. (EOS%R_to_kg_m3 == 1.0) .and. &
40224 (EOS%C_to_degC == 1.0) .and. (EOS%S_to_ppt == 1.0)) then
403 call EOS%type%calculate_density_array_2d(T, S, pressure, rho, domain, &
40424 rho_ref=rho_ref)
405 else ! This is the same as above, but with some extra work to rescale variables.
4060 is = domain(1,1) ; ie = domain(1,2)
4070 js = domain(2,1) ; je = domain(2,2)
408
4090 pres(is:ie, js:je) = EOS%RL2_T2_to_Pa * pressure(is:ie, js:je)
4100 Ta(is:ie, js:je) = EOS%C_to_degC * T(is:ie, js:je)
4110 Sa(is:ie, js:je) = EOS%S_to_ppt * S(is:ie, js:je)
412
4130 if (present(rho_ref)) then
414 call EOS%type%calculate_density_array_2d(Ta, Sa, pres, rho, domain, &
4150 rho_ref=EOS%R_to_kg_m3*rho_ref)
416 else
4170 call EOS%type%calculate_density_array_2d(Ta, Sa, pres, rho, domain)
418 endif
419 endif
420
42124 if (EOS%kg_m3_to_R /= 1.) &
4220 rho(is:ie, js:je) = EOS%kg_m3_to_R * rho(is:ie, js:je)
42324end subroutine calculate_density_2d
424
425
426!> Calls the appropriate subroutine to calculate density of sea water for 3-D array inputs.
427329400subroutine calculate_density_3d(T, S, pressure, rho, EOS, dom, rho_ref)
428 real, intent(in) :: T(:,:,:)
429 !< Potential temperature referenced to the surface [C ~> degC]
430 real, intent(in) :: S(:,:,:)
431 !< Salinity [S ~> ppt]
432 real, intent(in) :: pressure(:,:,:)
433 !< Pressure [R L2 T-2 ~> Pa]
434 real, intent(inout) :: rho(:,:,:)
435 !< Density (in-situ if pressure is local) [R ~> kg m-3]
436 type(EOS_type), intent(in) :: EOS
437 !< Equation of state structure
438 integer, optional, intent(in) :: dom(3,2)
439 !< The domain of indices to work on, taking into account that arrays start
440 !! at 1. The first index is the rank (i, j, k) and the second is the bound
441 !! (1 = lower, 2 = upper).
442 real, optional, intent(in) :: rho_ref
443 !< A reference density [R ~> kg m-3]
444
445658800 real, dimension(size(rho,1), size(rho,2), size(rho,3)) :: pres
446 ! Pressure converted to [Pa]
447658800 real, dimension(size(rho,1), size(rho,2), size(rho,3)) :: Ta
448 ! Temperature converted to [degC]
449658800 real, dimension(size(rho,1), size(rho,2), size(rho,3)) :: Sa
450 ! Salinity converted to [ppt]
451 integer :: is, ie, js, je, ks, ke
452 integer :: domain(3,2)
453
454329400 if (present(dom)) then
455329400 domain(:,:) = dom(:,:)
456 else
4570 domain(1,:) = [1, size(rho,1)]
4580 domain(2,:) = [1, size(rho,2)]
4590 domain(3,:) = [1, size(rho,3)]
460 endif
461
462329400 is = domain(1,1) ; ie = domain(1,2)
463329400 js = domain(2,1) ; je = domain(2,2)
464329400 ks = domain(3,1) ; ke = domain(3,2)
465
466 if ((EOS%RL2_T2_to_Pa == 1.0) .and. (EOS%R_to_kg_m3 == 1.0) .and. &
467329400 (EOS%C_to_degC == 1.0) .and. (EOS%S_to_ppt == 1.0)) then
468 call EOS%type%calculate_density_array_3d(T, S, pressure, rho, domain, &
469329400 rho_ref=rho_ref)
470 else ! This is the same as above, but with some extra work to rescale variables.
4710 pres(is:ie, js:je, ks:ke) = EOS%RL2_T2_to_Pa * pressure(is:ie, js:je, ks:ke)
4720 Ta(is:ie, js:je, ks:ke) = EOS%C_to_degC * T(is:ie, js:je, ks:ke)
4730 Sa(is:ie, js:je, ks:ke) = EOS%S_to_ppt * S(is:ie, js:je, ks:ke)
474
4750 if (present(rho_ref)) then
476 call EOS%type%calculate_density_array_3d(Ta, Sa, pres, rho, domain, &
4770 rho_ref=EOS%R_to_kg_m3*rho_ref)
478 else
4790 call EOS%type%calculate_density_array_3d(Ta, Sa, pres, rho, domain)
480 endif
481 endif
482
483329400 if (EOS%kg_m3_to_R /= 1.) &
4840 rho(is:ie, js:je, ks:ke) = EOS%kg_m3_to_R * rho(is:ie, js:je, ks:ke)
485329400end subroutine calculate_density_3d
486
487
488!> Calls the appropriate subroutine to calculate the density of sea water for 1-D array inputs
489!! including the variance of T, S and covariance of T-S,
490!! potentially limiting the domain of indices that are worked on.
491!! The calculation uses only the second order correction in a series as discussed
492!! in Stanley et al., 2020.
493!! If rho_ref is present, the anomaly with respect to rho_ref is returned.
4940subroutine calculate_stanley_density_1d(T, S, pressure, Tvar, TScov, Svar, rho, EOS, dom, rho_ref, scale)
495 real, dimension(:), intent(in) :: T !< Potential temperature referenced to the surface [C ~> degC]
496 real, dimension(:), intent(in) :: S !< Salinity [S ~> ppt]
497 real, dimension(:), intent(in) :: pressure !< Pressure [R L2 T-2 ~> Pa]
498 real, dimension(:), intent(in) :: Tvar !< Variance of potential temperature [C2 ~> degC2]
499 real, dimension(:), intent(in) :: TScov !< Covariance of potential temperature and salinity [C S ~> degC ppt]
500 real, dimension(:), intent(in) :: Svar !< Variance of salinity [S2 ~> ppt2]
501 real, dimension(:), intent(inout) :: rho !< Density (in-situ if pressure is local) [R ~> kg m-3]
502 type(EOS_type), intent(in) :: EOS !< Equation of state structure
503 integer, dimension(2), optional, intent(in) :: dom !< The domain of indices to work on, taking
504 !! into account that arrays start at 1.
505 real, optional, intent(in) :: rho_ref !< A reference density [R ~> kg m-3]
506 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale density
507 !! in combination with scaling stored in EOS [various]
508 ! Local variables
509 real, dimension(size(T)) :: &
5100 d2RdTT, & ! Second derivative of density with temperature [R C-2 ~> kg m-3 degC-2]
5110 d2RdST, & ! Second derivative of density with temperature and salinity [R S-1 C-1 ~> kg m-3 degC-1 ppt-1]
5120 d2RdSS, & ! Second derivative of density with salinity [R S-2 ~> kg m-3 ppt-2]
5130 d2RdSp, & ! Second derivative of density with salinity and pressure [T2 S-1 L-2 ~> kg m-3 ppt-1 Pa-1]
5140 d2RdTp ! Second derivative of density with temperature and pressure [T2 C-1 L-2 ~> kg m-3 degC-1 Pa-1]
515 integer :: i, is, ie, npts
516
5170 if (present(dom)) then
5180 is = dom(1) ; ie = dom(2) ; npts = 1 + ie - is
519 else
5200 is = 1 ; ie = size(rho) ; npts = 1 + ie - is
521 endif
522
5230 call calculate_density_1d(T, S, pressure, rho, EOS, dom, rho_ref)
5240 call calculate_density_second_derivs_1d(T, S, pressure, d2RdSS, d2RdST, d2RdTT, d2RdSp, d2RdTP, EOS, dom)
525
526 ! Equation 25 of Stanley et al., 2020.
5270 do i=is,ie
5280 rho(i) = rho(i) + ( 0.5 * d2RdTT(i) * Tvar(i) + ( d2RdST(i) * TScov(i) + 0.5 * d2RdSS(i) * Svar(i) ) )
529 enddo
530
5310 if (present(scale)) then ; if (scale /= 1.0) then ; do i=is,ie
5320 rho(i) = scale * rho(i)
533 enddo ; endif ; endif
534
5350end subroutine calculate_stanley_density_1d
536
537!> Calls the Stanley density routine for each row of 2-D array inputs.
5380subroutine calculate_stanley_density_2d(T, S, pressure, Tvar, TScov, Svar, rho, EOS, dom, rho_ref, scale)
539 real, dimension(:,:), intent(in) :: T !< Potential temperature referenced to the surface [C ~> degC]
540 real, dimension(:,:), intent(in) :: S !< Salinity [S ~> ppt]
541 real, dimension(:,:), intent(in) :: pressure !< Pressure [R L2 T-2 ~> Pa]
542 real, dimension(:,:), intent(in) :: Tvar !< Variance of potential temperature [C2 ~> degC2]
543 real, dimension(:,:), intent(in) :: TScov !< Covariance of potential temperature and salinity [C S ~> degC ppt]
544 real, dimension(:,:), intent(in) :: Svar !< Variance of salinity [S2 ~> ppt2]
545 real, dimension(:,:), intent(inout) :: rho !< Density (in-situ if pressure is local) [R ~> kg m-3]
546 type(EOS_type), intent(in) :: EOS !< Equation of state structure
547 integer, dimension(2,2), optional, intent(in) :: dom !< The domain of indices to work on, taking
548 !! into account that arrays start at 1.
549 real, optional, intent(in) :: rho_ref !< A reference density [R ~> kg m-3]
550 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale density
551 !! in combination with scaling stored in EOS [various]
552 integer :: j, js, je
553 integer, dimension(2) :: dom_row
554
5550 if (present(dom)) then
5560 js = dom(2,1) ; je = dom(2,2)
5570 dom_row = [dom(1,1), dom(1,2)]
558 else
5590 js = 1 ; je = size(rho,2)
5600 dom_row = [1, size(rho,1)]
561 endif
562
5630 do j=js,je
564 call calculate_stanley_density_1d(T(:,j), S(:,j), pressure(:,j), &
565 Tvar(:,j), TScov(:,j), Svar(:,j), &
5660 rho(:,j), EOS, dom_row, rho_ref, scale)
567 enddo
5680end subroutine calculate_stanley_density_2d
569
570!> Calls the appropriate subroutine to calculate the specific volume of sea water
571!! for 1-D array inputs.
5720subroutine calculate_spec_vol_array(T, S, pressure, specvol, start, npts, EOS, spv_ref, scale)
573 real, dimension(:), intent(in) :: T !< potential temperature relative to the surface [degC]
574 real, dimension(:), intent(in) :: S !< salinity [ppt]
575 real, dimension(:), intent(in) :: pressure !< pressure [Pa]
576 real, dimension(:), intent(inout) :: specvol !< in situ specific volume [kg m-3]
577 integer, intent(in) :: start !< the starting point in the arrays.
578 integer, intent(in) :: npts !< the number of values to calculate.
579 type(EOS_type), intent(in) :: EOS !< Equation of state structure
580 real, optional, intent(in) :: spv_ref !< A reference specific volume [m3 kg-1]
581 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale specific
582 !! volume in combination with scaling stored in EOS [various]
583
584 integer :: j
585
5860 if (.not. allocated(EOS%type)) call MOM_error(FATAL, &
5870 "calculate_spec_vol_array: EOS%form_of_EOS is not valid.")
588
5890 call EOS%type%calculate_spec_vol_array(T, S, pressure, specvol, start, npts, spv_ref)
590
5910 if (present(scale)) then ; if (scale /= 1.0) then ; do j=start,start+npts-1
5920 specvol(j) = scale * specvol(j)
593 enddo ; endif ; endif
594
5950end subroutine calculate_spec_vol_array
596
597!> Calls the appropriate subroutine to calculate specific volume of sea water
598!! for scalar inputs.
5990subroutine calc_spec_vol_scalar(T, S, pressure, specvol, EOS, spv_ref, scale)
600 real, intent(in) :: T !< Potential temperature referenced to the surface [C ~> degC]
601 real, intent(in) :: S !< Salinity [S ~> ppt]
602 real, intent(in) :: pressure !< Pressure [R L2 T-2 ~> Pa]
603 real, intent(out) :: specvol !< In situ or potential specific volume [R-1 ~> m3 kg-1]
604 !! or other units determined by the scale argument
605 type(EOS_type), intent(in) :: EOS !< Equation of state structure
606 real, optional, intent(in) :: spv_ref !< A reference specific volume [R-1 ~> m3 kg-1]
607 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale specific
608 !! volume in combination with scaling stored in EOS [various]
609
610 real, dimension(1) :: Ta ! Rescaled single element array version of temperature [degC]
611 real, dimension(1) :: Sa ! Rescaled single element array version of salinity [ppt]
612 real, dimension(1) :: pres ! Rescaled single element array version of pressure [Pa]
613 real, dimension(1) :: spv ! Rescaled single element array version of specific volume [m3 kg-1]
614 real :: spv_scale ! A factor to convert specific volume from m3 kg-1 to the desired units [kg R-1 m-3 ~> 1]
615
6160 pres(1) = EOS%RL2_T2_to_Pa * pressure
6170 Ta(1) = EOS%C_to_degC * T ; Sa(1) = EOS%S_to_ppt * S
618
6190 if (present(spv_ref)) then
6200 call calculate_spec_vol_array(Ta, Sa, pres, spv, 1, 1, EOS, EOS%kg_m3_to_R*spv_ref)
621 else
6220 call calculate_spec_vol_array(Ta, Sa, pres, spv, 1, 1, EOS)
623 endif
6240 specvol = spv(1)
625
6260 spv_scale = EOS%R_to_kg_m3
6270 if (present(scale)) spv_scale = spv_scale * scale
6280 if (spv_scale /= 1.0) then
6290 specvol = spv_scale * specvol
630 endif
631
6320end subroutine calc_spec_vol_scalar
633
634!> Calls the appropriate subroutine to calculate the specific volume of sea water for 1-D array
635!! inputs, potentially limiting the domain of indices that are worked on.
6360subroutine calc_spec_vol_1d(T, S, pressure, specvol, EOS, dom, spv_ref, scale)
637 real, dimension(:), intent(in) :: T !< Potential temperature referenced to the surface [C ~> degC]
638 real, dimension(:), intent(in) :: S !< Salinity [S ~> ppt]
639 real, dimension(:), intent(in) :: pressure !< Pressure [R L2 T-2 ~> Pa]
640 real, dimension(:), intent(inout) :: specvol !< In situ specific volume [R-1 ~> m3 kg-1]
641 type(EOS_type), intent(in) :: EOS !< Equation of state structure
642 integer, dimension(2), optional, intent(in) :: dom !< The domain of indices to work on, taking
643 !! into account that arrays start at 1.
644 real, optional, intent(in) :: spv_ref !< A reference specific volume [R-1 ~> m3 kg-1]
645 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale
646 !! output specific volume in combination with
647 !! scaling stored in EOS [various]
648 ! Local variables
6490 real, dimension(size(T)) :: pres ! Pressure converted to [Pa]
6500 real, dimension(size(T)) :: Ta ! Temperature converted to [degC]
6510 real, dimension(size(T)) :: Sa ! Salinity converted to [ppt]
652 real :: spv_scale ! A factor to convert specific volume from m3 kg-1 to the desired units [kg m-3 R-1 ~> 1]
653 integer :: i, is, ie, npts
654
6550 if (present(dom)) then
6560 is = dom(1) ; ie = dom(2) ; npts = 1 + ie - is
657 else
6580 is = 1 ; ie = size(specvol) ; npts = 1 + ie - is
659 endif
660
661 if ((EOS%RL2_T2_to_Pa == 1.0) .and. (EOS%kg_m3_to_R == 1.0) .and. &
6620 (EOS%C_to_degC == 1.0) .and. (EOS%S_to_ppt == 1.0)) then
6630 call calculate_spec_vol_array(T, S, pressure, specvol, is, npts, EOS, spv_ref)
664 else ! This is the same as above, but with some extra work to rescale variables.
6650 do i=is,ie
6660 pres(i) = EOS%RL2_T2_to_Pa * pressure(i)
6670 Ta(i) = EOS%C_to_degC * T(i)
6680 Sa(i) = EOS%S_to_ppt * S(i)
669 enddo
6700 if (present(spv_ref)) then
6710 call calculate_spec_vol_array(Ta, Sa, pres, specvol, is, npts, EOS, EOS%kg_m3_to_R*spv_ref)
672 else
673 ! There is rescaling of variables, but spv_ref is not present. Passing a 0 value of spv_ref
674 ! changes answers at roundoff for some equations of state, like Wright and UNESCO.
6750 call calculate_spec_vol_array(Ta, Sa, pres, specvol, is, npts, EOS)
676 endif
677 endif
678
6790 spv_scale = EOS%R_to_kg_m3
6800 if (present(scale)) spv_scale = spv_scale * scale
6810 if (spv_scale /= 1.0) then ; do i=is,ie
6820 specvol(i) = spv_scale * specvol(i)
683 enddo ; endif
684
6850end subroutine calc_spec_vol_1d
686
687
688!> Calls the appropriate subroutine to calculate the freezing point for scalar inputs.
6890subroutine calculate_TFreeze_scalar(S, pressure, T_fr, EOS, pres_scale, scale_from_EOS)
690 real, intent(in) :: S !< Salinity, [ppt] or [S ~> ppt] depending on scale_from_EOS
691 real, intent(in) :: pressure !< Pressure, in [Pa] or [R L2 T-2 ~> Pa] depending on
692 !! pres_scale or scale_from_EOS
693 real, intent(out) :: T_fr !< Freezing point potential temperature referenced to the
694 !! surface [degC] or [C ~> degC] depending on scale_from_EOS
695 type(EOS_type), intent(in) :: EOS !< Equation of state structure
696 real, optional, intent(in) :: pres_scale !< A multiplicative factor to convert pressure
697 !! into Pa [Pa T2 R-1 L-2 ~> 1].
698 logical, optional, intent(in) :: scale_from_EOS !< If present true use the dimensional scaling
699 !! factors stored in EOS. Omission is the same .false.
700
701 ! Local variables
702 real :: p_scale ! A factor to convert pressure to units of Pa [Pa T2 R-1 L-2 ~> 1]
703 real :: S_scale ! A factor to convert salinity to units of ppt [ppt S-1 ~> 1]
704 real :: iS_scale! A factor to convert salinity to units of S [S ppt-1 ~> 1]
705 real :: absS ! A salinity converted to absolute salinity, only used in specific scenarios [ppt]
706 real :: TFreeze_S ! The salinity for the freezing equation in model units [S ~> PSU or ppt]
707
7080 p_scale = 1.0 ; S_scale = 1.0 ; iS_scale = 1.0
7090 if (present(pres_scale)) p_scale = pres_scale
7100 if (present(scale_from_EOS)) then ; if (scale_from_EOS) then
7110 p_scale = EOS%RL2_T2_to_Pa
7120 S_scale = EOS%S_to_ppt
7130 iS_scale = EOS%ppt_to_S
714 endif ; endif
715
7160 if (EOS%use_conT_absS) then
717 ! Otherwise absS is unneeded and therefore unset
7180 absS = S*S_scale
7190 if (EOS%TFreeze_S_is_pracS) then
7200 TFreeze_S = gsw_sp_from_sr(absS)*iS_scale
721 else
7220 TFreeze_S = S
723 endif
724 else
7250 TFreeze_S = S
726 endif
727
7280 select case (EOS%form_of_TFreeze)
729 case (TFREEZE_LINEAR)
730 call calculate_TFreeze_linear(S_scale*TFreeze_S, p_scale*pressure, T_fr, EOS%TFr_S0_P0, &
7310 EOS%dTFr_dS, EOS%dTFr_dp)
732 case (TFREEZE_MILLERO)
7330 call calculate_TFreeze_Millero(S_scale*TFreeze_S, p_scale*pressure, T_fr)
734 case (TFREEZE_TEOSPOLY)
7350 call calculate_TFreeze_TEOS_poly(S_scale*TFreeze_S, p_scale*pressure, T_fr)
736 case (TFREEZE_TEOS10)
7370 call calculate_TFreeze_teos10(S_scale*TFreeze_S, p_scale*pressure, T_fr)
738 case default
7390 call MOM_error(FATAL, "calculate_TFreeze_scalar: form_of_TFreeze is not valid.")
740 end select
741
7420 if (EOS%use_conT_absS .and. EOS%TFreeze_T_is_potT) then
743 ! absS is set only if EOS%use_conT_absS is True
744 ! absS and T_fr have physical units here and don't need converted
7450 T_fr = gsw_ct_from_pt(absS,T_fr)
746 endif
747
7480 if (present(scale_from_EOS)) then ; if (scale_from_EOS) then
7490 T_fr = EOS%degC_to_C * T_fr
750 endif ; endif
751
7520end subroutine calculate_TFreeze_scalar
753
754!> Calls the appropriate subroutine to calculate the freezing point for a 1-D array.
7550subroutine calculate_TFreeze_array(S, pressure, T_fr, start, npts, EOS, pres_scale)
756 real, dimension(:), intent(in) :: S !< Salinity [ppt]
757 real, dimension(:), intent(in) :: pressure !< Pressure, in [Pa] or [R L2 T-2 ~> Pa] depending on pres_scale
758 real, dimension(:), intent(inout) :: T_fr !< Freezing point, either potential temperature referenced to the
759 !! surface or conservative temperature depending on settings [degC]
760 integer, intent(in) :: start !< Starting index within the array
761 integer, intent(in) :: npts !< The number of values to calculate
762 type(EOS_type), intent(in) :: EOS !< Equation of state structure
763 real, optional, intent(in) :: pres_scale !< A multiplicative factor to convert pressure
764 !! into Pa [Pa T2 R-1 L-2 ~> 1].
765
766 ! Local variables
7670 real, dimension(size(pressure)) :: pres ! Pressure converted to [Pa]
768 real :: p_scale ! A factor to convert pressure to units of Pa [Pa T2 R-1 L-2 ~> 1]
7690 real, dimension(size(S)) :: absS ! A salinity converted to absolute salinity, only used in specific scenarios [ppt]
7700 real, dimension(size(S)) :: TFreeze_S ! The salinity for the freezing equation in model units [S ~> PSU or ppt]
771 integer :: j
772
7730 p_scale = 1.0 ; if (present(pres_scale)) p_scale = pres_scale
774
7750 if (EOS%use_conT_absS) then
776 ! Otherwise absS is unneeded and therefore unset
7770 absS(:) = S(:)
7780 if (EOS%TFreeze_S_is_pracS) then
7790 TFreeze_S(:) = gsw_sp_from_sr(absS(:))
780 else
7810 TFreeze_S(:) = S(:)
782 endif
783 else
7840 TFreeze_S(:) = S(:)
785 endif
786
7870 if (p_scale == 1.0) then
7880 select case (EOS%form_of_TFreeze)
789 case (TFREEZE_LINEAR)
790 call calculate_TFreeze_linear(TFreeze_S, pressure, T_fr, start, npts, &
7910 EOS%TFr_S0_P0, EOS%dTFr_dS, EOS%dTFr_dp)
792 case (TFREEZE_MILLERO)
7930 call calculate_TFreeze_Millero(TFreeze_S, pressure, T_fr, start, npts)
794 case (TFREEZE_TEOSPOLY)
7950 call calculate_TFreeze_TEOS_poly(TFreeze_S, pressure, T_fr, start, npts)
796 case (TFREEZE_TEOS10)
7970 call calculate_TFreeze_teos10(TFreeze_S, pressure, T_fr, start, npts)
798 case default
7990 call MOM_error(FATAL, "calculate_TFreeze_scalar: form_of_TFreeze is not valid.")
800 end select
801 else
8020 do j=start,start+npts-1 ; pres(j) = p_scale * pressure(j) ; enddo
8030 select case (EOS%form_of_TFreeze)
804 case (TFREEZE_LINEAR)
805 call calculate_TFreeze_linear(TFreeze_S, pres, T_fr, start, npts, &
8060 EOS%TFr_S0_P0, EOS%dTFr_dS, EOS%dTFr_dp)
807 case (TFREEZE_MILLERO)
8080 call calculate_TFreeze_Millero(TFreeze_S, pres, T_fr, start, npts)
809 case (TFREEZE_TEOS10)
8100 call calculate_TFreeze_teos10(TFreeze_S, pres, T_fr, start, npts)
811 case (TFREEZE_TEOSPOLY)
8120 call calculate_TFreeze_TEOS_poly(TFreeze_S, pres, T_fr, start, npts)
813 case default
8140 call MOM_error(FATAL, "calculate_TFreeze_scalar: form_of_TFreeze is not valid.")
815 end select
816 endif
817
8180 if (EOS%use_conT_absS .and. EOS%TFreeze_T_is_potT) then
819 ! absS is set only if EOS%use_conT_absS is True!
8200 T_fr(:) = gsw_ct_from_pt(absS(:),T_fr(:))
821 endif
822
823
8240end subroutine calculate_TFreeze_array
825
826!> Calls the appropriate subroutine to calculate the freezing point for a 1-D array, taking
827!! dimensionally rescaled arguments with factors stored in EOS.
8280subroutine calculate_TFreeze_1d(S, pressure, T_fr, EOS, dom)
829 real, dimension(:), intent(in) :: S !< Salinity [S ~> ppt]
830 real, dimension(:), intent(in) :: pressure !< Pressure [R L2 T-2 ~> Pa]
831 real, dimension(:), intent(inout) :: T_fr !< Freezing point, either potential temperature referenced to the
832 !! surface or conservative temperature depending on settings
833 !! [C ~> degC]
834 type(EOS_type), intent(in) :: EOS !< Equation of state structure
835 integer, dimension(2), optional, intent(in) :: dom !< The domain of indices to work on, taking
836 !! into account that arrays start at 1.
837
838 ! Local variables
8390 real, dimension(size(T_fr)) :: pres ! Pressure converted to [Pa]
8400 real, dimension(size(T_fr)) :: Sa ! Salinity converted to [ppt]
8410 real, dimension(size(T_fr)) :: absS ! Salinity converted to absoluate salinity [ppt]
8420 real, dimension(size(T_fr)) :: TFreeze_S ! The salinity for the freezing equation in model units [S ~> PSU or ppt]
843 integer :: i, is, ie, npts
844
8450 if (present(dom)) then
8460 is = dom(1) ; ie = dom(2) ; npts = 1 + ie - is
847 else
8480 is = 1 ; ie = size(T_Fr) ; npts = 1 + ie - is
849 endif
850
8510 if (EOS%use_conT_absS) then
852 ! Otherwise absS is unneeded and therefore unset
8530 absS(:) = S(:)*EOS%S_to_ppt
8540 if (EOS%TFreeze_S_is_pracS) then
8550 TFreeze_S(:) = gsw_sp_from_sr(absS(:))*EOS%ppt_to_S
856 else
8570 TFreeze_S(:) = S(:)
858 endif
859 else
8600 TFreeze_S(:) = S(:)
861 endif
862
8630 if ((EOS%RL2_T2_to_Pa == 1.0) .and. (EOS%S_to_ppt == 1.0)) then
8640 select case (EOS%form_of_TFreeze)
865 case (TFREEZE_LINEAR)
866 call calculate_TFreeze_linear(TFreeze_S, pressure, T_fr, is, npts, &
8670 EOS%TFr_S0_P0, EOS%dTFr_dS, EOS%dTFr_dp)
868 case (TFREEZE_MILLERO)
8690 call calculate_TFreeze_Millero(TFreeze_S, pressure, T_fr, is, npts)
870 case (TFREEZE_TEOSPOLY)
8710 call calculate_TFreeze_TEOS_poly(TFreeze_S, pressure, T_fr, is, npts)
872 case (TFREEZE_TEOS10)
8730 call calculate_TFreeze_teos10(TFreeze_S, pressure, T_fr, is, npts)
874 case default
8750 call MOM_error(FATAL, "calculate_TFreeze_scalar: form_of_TFreeze is not valid.")
876 end select
877 else
8780 do i=is,ie
8790 pres(i) = EOS%RL2_T2_to_Pa * pressure(i)
8800 Sa(i) = EOS%S_to_ppt * TFreeze_S(i)
881 enddo
8820 select case (EOS%form_of_TFreeze)
883 case (TFREEZE_LINEAR)
884 call calculate_TFreeze_linear(Sa, pres, T_fr, is, npts, &
8850 EOS%TFr_S0_P0, EOS%dTFr_dS, EOS%dTFr_dp)
886 case (TFREEZE_MILLERO)
8870 call calculate_TFreeze_Millero(Sa, pres, T_fr, is, npts)
888 case (TFREEZE_TEOSPOLY)
8890 call calculate_TFreeze_TEOS_poly(Sa, pres, T_fr, is, npts)
890 case (TFREEZE_TEOS10)
8910 call calculate_TFreeze_teos10(Sa, pres, T_fr, is, npts)
892 case default
8930 call MOM_error(FATAL, "calculate_TFreeze_scalar: form_of_TFreeze is not valid.")
894 end select
895 endif
896
8970 if (EOS%use_conT_absS .and. EOS%TFreeze_T_is_potT) then
898 ! absS is set only if EOS%use_conT_absS is True!
899 ! absS is in ppt and T_fr is in degC at this point.
9000 T_fr(:) = gsw_ct_from_pt(absS(:),T_fr(:))
901 endif
902
903
9040 if (EOS%degC_to_C /= 1.0) then
9050 do i=is,ie ; T_fr(i) = EOS%degC_to_C * T_fr(i) ; enddo
906 endif
907
9080end subroutine calculate_TFreeze_1d
909
910
911!> Calls the appropriate subroutine to calculate density derivatives for 1-D array inputs.
912402312subroutine calculate_density_derivs_array(T, S, pressure, drho_dT, drho_dS, start, npts, EOS, scale)
913 real, dimension(:), intent(in) :: T !< Potential temperature referenced to the surface [degC]
914 real, dimension(:), intent(in) :: S !< Salinity [ppt]
915 real, dimension(:), intent(in) :: pressure !< Pressure [Pa]
916 real, dimension(:), intent(inout) :: drho_dT !< The partial derivative of density with potential
917 !! temperature [kg m-3 degC-1] or other units determined
918 !! by the optional scale argument
919 real, dimension(:), intent(inout) :: drho_dS !< The partial derivative of density with salinity,
920 !! in [kg m-3 ppt-1] or other units determined
921 !! by the optional scale argument
922 integer, intent(in) :: start !< Starting index within the array
923 integer, intent(in) :: npts !< The number of values to calculate
924 type(EOS_type), intent(in) :: EOS !< Equation of state structure
925 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale density
926 !! in combination with scaling stored in EOS [various]
927
928 ! Local variables
929 integer :: j
930
931402312 if (.not. allocated(EOS%type)) call MOM_error(FATAL, &
9320 "calculate_density_derivs_array: EOS%form_of_EOS is not valid.")
933
934402312 call EOS%type%calculate_density_derivs_array(T, S, pressure, drho_dT, drho_dS, start, npts)
935
936402312 if (present(scale)) then ; if (scale /= 1.0) then ; do j=start,start+npts-1
9370 drho_dT(j) = scale * drho_dT(j)
9380 drho_dS(j) = scale * drho_dS(j)
939 enddo ; endif ; endif
940
941402312end subroutine calculate_density_derivs_array
942
943
944!> Calls the appropriate subroutine to calculate density derivatives for 1-D array inputs.
945402312subroutine calculate_density_derivs_1d(T, S, pressure, drho_dT, drho_dS, EOS, dom, scale)
946 real, dimension(:), intent(in) :: T !< Potential temperature referenced to the surface [C ~> degC]
947 real, dimension(:), intent(in) :: S !< Salinity [S ~> ppt]
948 real, dimension(:), intent(in) :: pressure !< Pressure [R L2 T-2 ~> Pa]
949 real, dimension(:), intent(inout) :: drho_dT !< The partial derivative of density with potential
950 !! temperature [R C-1 ~> kg m-3 degC-1]
951 real, dimension(:), intent(inout) :: drho_dS !< The partial derivative of density with salinity
952 !! [R S-1 ~> kg m-3 ppt-1]
953 type(EOS_type), intent(in) :: EOS !< Equation of state structure
954 integer, dimension(2), optional, intent(in) :: dom !< The domain of indices to work on, taking
955 !! into account that arrays start at 1.
956 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale density
957 !! in combination with scaling stored in EOS [various]
958 ! Local variables
959804624 real, dimension(size(drho_dT)) :: pres ! Pressure converted to [Pa]
960804624 real, dimension(size(drho_dT)) :: Ta ! Temperature converted to [degC]
961804624 real, dimension(size(drho_dT)) :: Sa ! Salinity converted to [ppt]
962 real :: rho_scale ! A factor to convert density from kg m-3 to the desired units [R m3 kg-1 ~> 1]
963 real :: dRdT_scale ! A factor to convert drho_dT to the desired units [R degC m3 C-1 kg-1 ~> 1]
964 real :: dRdS_scale ! A factor to convert drho_dS to the desired units [R ppt m3 S-1 kg-1 ~> 1]
965 integer :: i, is, ie, npts
966
967402312 if (present(dom)) then
968402312 is = dom(1) ; ie = dom(2) ; npts = 1 + ie - is
969 else
9700 is = 1 ; ie = size(drho_dT) ; npts = 1 + ie - is
971 endif
972
973402312 if ((EOS%RL2_T2_to_Pa == 1.0) .and. (EOS%C_to_degC == 1.0) .and. (EOS%S_to_ppt == 1.0)) then
974402312 call calculate_density_derivs_array(T, S, pressure, drho_dT, drho_dS, is, npts, EOS)
975 else
9760 do i=is,ie
9770 pres(i) = EOS%RL2_T2_to_Pa * pressure(i)
9780 Ta(i) = EOS%C_to_degC * T(i)
9790 Sa(i) = EOS%S_to_ppt * S(i)
980 enddo
9810 call calculate_density_derivs_array(Ta, Sa, pres, drho_dT, drho_dS, is, npts, EOS)
982 endif
983
984402312 rho_scale = EOS%kg_m3_to_R
985402312 if (present(scale)) rho_scale = rho_scale * scale
986402312 dRdT_scale = rho_scale * EOS%C_to_degC
987402312 dRdS_scale = rho_scale * EOS%S_to_ppt
9883624324 if ((dRdT_scale /= 1.0) .or. (dRdS_scale /= 1.0)) then ; do i=is,ie
9893222012 drho_dT(i) = dRdT_scale * drho_dT(i)
9903283800 drho_dS(i) = dRdS_scale * drho_dS(i)
991 enddo ; endif
992
993402312end subroutine calculate_density_derivs_1d
994
995
996!> Calls the appropriate subroutine to calculate density derivatives for 1-D array inputs.
99724subroutine calculate_density_derivs_2d(T, S, pressure, drho_dT, drho_dS, EOS, dom)
998 real, intent(in) :: T(:,:)
999 !< Potential temperature referenced to the surface [degC]
1000 real, intent(in) :: S(:,:)
1001 !< Salinity [ppt]
1002 real, intent(in) :: pressure(:,:)
1003 !< Pressure [Pa]
1004 real, intent(inout) :: drho_dT(:,:)
1005 !< The partial derivative of density with potential temperature
1006 !! [kg m-3 degC-1] or other units determinedby the optional scale argument
1007 real, intent(inout) :: drho_dS(:,:)
1008 !< The partial derivative of density with salinity, in [kg m-3 ppt-1] or
1009 !! other units determined by the optional scale argument
1010 type(EOS_type), intent(in) :: EOS
1011 !< Equation of state structure
1012 integer, optional, intent(in) :: dom(2,2)
1013 !< The domain of indices to work on, taking into account that arrays start
1014
1015 ! Local variables
101648 real :: Ta(size(T,1), size(T,2))
1017 ! Temperature converted to [degC]
101848 real :: Sa(size(S,1), size(S,2))
1019 ! Salinity converted to [ppt]
102048 real :: press(size(pressure,1), size(pressure,2))
1021 ! Pressure converted to [Pa]
1022 integer :: is, ie, js, je, npts
1023 integer :: domain(2,2)
1024
102524 if (present(dom)) then
102624 domain(:,:) = dom(:,:)
1027 else
10280 domain(1,:) = [1, size(drho_dT, 1)]
10290 domain(2,:) = [1, size(drho_dT, 2)]
1030 endif
103124 is = domain(1,1) ; ie = domain(1,2)
103224 js = domain(2,1) ; je = domain(2,2)
1033
103424 if (.not. allocated(EOS%type)) call MOM_error(FATAL, &
10350 "calculate_density_derivs_array: EOS%form_of_EOS is not valid.")
1036
103796 if (all([EOS%RL2_T2_to_Pa, EOS%C_to_degC, EOS%S_to_ppt] == 1.)) then
103824 call EOS%type%calculate_density_derivs_2d(T, S, pressure, drho_dT, drho_dS, domain)
1039 else
10400 press(is:ie, js:je) = EOS%RL2_T2_to_Pa * pressure(is:ie, js:je)
10410 Ta(is:ie, js:je) = EOS%C_to_degC * T(is:ie, js:je)
10420 Sa(is:ie, js:je) = EOS%S_to_ppt * S(is:ie, js:je)
1043
10440 call EOS%type%calculate_density_derivs_2d(Ta, Sa, press, drho_dT, drho_dS, domain)
1045 endif
1046
104724 if (EOS%kg_m3_to_R * EOS%C_to_degC /= 1.) &
10480 drho_dT(is:ie, js:je) = EOS%kg_m3_to_R * EOS%C_to_degC * drho_dT(is:ie, js:je)
104924 if (EOS%kg_m3_to_R * EOS%S_to_ppt /= 1.) &
10500 drho_dS(is:ie, js:je) = EOS%kg_m3_to_R * EOS%S_to_ppt * drho_dS(is:ie, js:je)
105124end subroutine calculate_density_derivs_2d
1052
1053
1054!> Calls the appropriate subroutine to calculate density derivatives for 3-D array inputs.
1055223776subroutine calculate_density_derivs_3d(T, S, pressure, drho_dT, drho_dS, EOS, dom)
1056 real, intent(in) :: T(:,:,:)
1057 !< Potential temperature referenced to the surface [degC]
1058 real, intent(in) :: S(:,:,:)
1059 !< Salinity [ppt]
1060 real, intent(in) :: pressure(:,:,:)
1061 !< Pressure [Pa]
1062 real, intent(inout) :: drho_dT(:,:,:)
1063 !< The partial derivative of density with potential temperature
1064 !! [kg m-3 degC-1] or other units determined by the optional scale argument
1065 real, intent(inout) :: drho_dS(:,:,:)
1066 !< The partial derivative of density with salinity, in [kg m-3 ppt-1] or
1067 !! other units determined by the optional scale argument
1068 type(EOS_type), intent(in) :: EOS
1069 !< Equation of state structure
1070 integer, optional, intent(in) :: dom(3,2)
1071 !< The domain of indices to work on, taking into account that arrays start
1072 !! at 1. The first index is the rank (i, j, k) and the second is the bound
1073 !! (1 = lower, 2 = upper).
1074
1075 ! Local variables
1076447552 real :: Ta(size(T,1), size(T,2), size(T,3))
1077 ! Temperature converted to [degC]
1078447552 real :: Sa(size(S,1), size(S,2), size(S,3))
1079 ! Salinity converted to [ppt]
1080447552 real :: press(size(pressure,1), size(pressure,2), size(pressure,3))
1081 ! Pressure converted to [Pa]
1082 integer :: is, ie, js, je, ks, ke
1083 integer :: domain(3,2)
1084
1085223776 if (present(dom)) then
1086223776 domain(:,:) = dom(:,:)
1087 else
10880 domain(1,:) = [1, size(drho_dT, 1)]
10890 domain(2,:) = [1, size(drho_dT, 2)]
10900 domain(3,:) = [1, size(drho_dT, 3)]
1091 endif
1092223776 is = domain(1,1) ; ie = domain(1,2)
1093223776 js = domain(2,1) ; je = domain(2,2)
1094223776 ks = domain(3,1) ; ke = domain(3,2)
1095
1096223776 if (.not. allocated(EOS%type)) call MOM_error(FATAL, &
10970 "calculate_density_derivs_3d: EOS%form_of_EOS is not valid.")
1098
1099895104 if (all([EOS%RL2_T2_to_Pa, EOS%C_to_degC, EOS%S_to_ppt] == 1.)) then
1100223776 call EOS%type%calculate_density_derivs_3d(T, S, pressure, drho_dT, drho_dS, domain)
1101 else
11020 press(is:ie, js:je, ks:ke) = EOS%RL2_T2_to_Pa * pressure(is:ie, js:je, ks:ke)
11030 Ta(is:ie, js:je, ks:ke) = EOS%C_to_degC * T(is:ie, js:je, ks:ke)
11040 Sa(is:ie, js:je, ks:ke) = EOS%S_to_ppt * S(is:ie, js:je, ks:ke)
1105
11060 call EOS%type%calculate_density_derivs_3d(Ta, Sa, press, drho_dT, drho_dS, domain)
1107 endif
1108
1109223776 if (EOS%kg_m3_to_R * EOS%C_to_degC /= 1.) &
11100 drho_dT(is:ie, js:je, ks:ke) = EOS%kg_m3_to_R * EOS%C_to_degC * drho_dT(is:ie, js:je, ks:ke)
1111223776 if (EOS%kg_m3_to_R * EOS%S_to_ppt /= 1.) &
11120 drho_dS(is:ie, js:je, ks:ke) = EOS%kg_m3_to_R * EOS%S_to_ppt * drho_dS(is:ie, js:je, ks:ke)
1113223776end subroutine calculate_density_derivs_3d
1114
1115
1116!> Calls the appropriate subroutines to calculate density derivatives by promoting a scalar
1117!! to a one-element array
11180subroutine calculate_density_derivs_scalar(T, S, pressure, drho_dT, drho_dS, EOS, scale)
1119 real, intent(in) :: T !< Potential temperature referenced to the surface [C ~> degC]
1120 real, intent(in) :: S !< Salinity [S ~> ppt]
1121 real, intent(in) :: pressure !< Pressure [R L2 T-2 ~> Pa]
1122 real, intent(out) :: drho_dT !< The partial derivative of density with potential
1123 !! temperature [R C-1 ~> kg m-3 degC-1] or other
1124 !! units determined by the optional scale argument
1125 real, intent(out) :: drho_dS !< The partial derivative of density with salinity,
1126 !! in [R S-1 ~> kg m-3 ppt-1] or other units
1127 !! determined by the optional scale argument
1128 type(EOS_type), intent(in) :: EOS !< Equation of state structure
1129 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale density
1130 !! in combination with scaling stored in EOS [various]
1131 ! Local variables
1132 real :: rho_scale ! A factor to convert density from kg m-3 to the desired units [R m3 kg-1 ~> 1]
1133 real :: dRdT_scale ! A factor to convert drho_dT to the desired units [R degC m3 C-1 kg-1 ~> 1]
1134 real :: dRdS_scale ! A factor to convert drho_dS to the desired units [R ppt m3 S-1 kg-1 ~> 1]
1135 real :: pres(1) ! Pressure converted to [Pa]
1136 real :: Ta(1) ! Temperature converted to [degC]
1137 real :: Sa(1) ! Salinity converted to [ppt]
1138
11390 pres(1) = EOS%RL2_T2_to_Pa*pressure
11400 Ta(1) = EOS%C_to_degC * T
11410 Sa(1) = EOS%S_to_ppt * S
1142
11430 call EOS%type%calculate_density_derivs_scalar(Ta(1), Sa(1), pres(1), drho_dT, drho_dS)
1144
11450 rho_scale = EOS%kg_m3_to_R
11460 if (present(scale)) rho_scale = rho_scale * scale
11470 dRdT_scale = rho_scale * EOS%C_to_degC
11480 dRdS_scale = rho_scale * EOS%S_to_ppt
11490 if ((dRdT_scale /= 1.0) .or. (dRdS_scale /= 1.0)) then
11500 drho_dT = dRdT_scale * drho_dT
11510 drho_dS = dRdS_scale * drho_dS
1152 endif
1153
11540end subroutine calculate_density_derivs_scalar
1155
1156!> Calls the appropriate subroutine to calculate density second derivatives for 1-D array inputs.
11570subroutine calculate_density_second_derivs_1d(T, S, pressure, drho_dS_dS, drho_dS_dT, drho_dT_dT, &
11580 drho_dS_dP, drho_dT_dP, EOS, dom, scale)
1159 real, dimension(:), intent(in) :: T !< Potential temperature referenced to the surface [C ~> degC]
1160 real, dimension(:), intent(in) :: S !< Salinity [S ~> ppt]
1161 real, dimension(:), intent(in) :: pressure !< Pressure [R L2 T-2 ~> Pa]
1162 real, dimension(:), intent(inout) :: drho_dS_dS !< Partial derivative of beta with respect to S
1163 !! [R S-2 ~> kg m-3 ppt-2]
1164 real, dimension(:), intent(inout) :: drho_dS_dT !< Partial derivative of beta with respect to T
1165 !! [R S-1 C-1 ~> kg m-3 ppt-1 degC-1]
1166 real, dimension(:), intent(inout) :: drho_dT_dT !< Partial derivative of alpha with respect to T
1167 !! [R C-2 ~> kg m-3 degC-2]
1168 real, dimension(:), intent(inout) :: drho_dS_dP !< Partial derivative of beta with respect to pressure
1169 !! [T2 S-1 L-2 ~> kg m-3 ppt-1 Pa-1]
1170 real, dimension(:), intent(inout) :: drho_dT_dP !< Partial derivative of alpha with respect to pressure
1171 !! [T2 C-1 L-2 ~> kg m-3 degC-1 Pa-1]
1172 type(EOS_type), intent(in) :: EOS !< Equation of state structure
1173 integer, dimension(2), optional, intent(in) :: dom !< The domain of indices to work on, taking
1174 !! into account that arrays start at 1.
1175 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale density
1176 !! in combination with scaling stored in EOS [various]
1177 ! Local variables
11780 real, dimension(size(T)) :: pres ! Pressure converted to [Pa]
11790 real, dimension(size(T)) :: Ta ! Temperature converted to [degC]
11800 real, dimension(size(T)) :: Sa ! Salinity converted to [ppt]
1181 real :: rho_scale ! A factor to convert density from kg m-3 to the desired units [R m3 kg-1 ~> 1]
1182 integer :: i, is, ie, npts
1183
11840 if (.not. allocated(EOS%type)) call MOM_error(FATAL, &
11850 "calculate_density_second_derivs: EOS%form_of_EOS is not valid.")
1186
11870 if (present(dom)) then
11880 is = dom(1) ; ie = dom(2) ; npts = 1 + ie - is
1189 else
11900 is = 1 ; ie = size(T) ; npts = 1 + ie - is
1191 endif
1192
11930 if ((EOS%RL2_T2_to_Pa == 1.0) .and. (EOS%C_to_degC == 1.0) .and. (EOS%S_to_ppt == 1.0)) then
1194 call EOS%type%calculate_density_second_derivs_array(T, S, pressure, &
11950 drho_dS_dS, drho_dS_dT, drho_dT_dT, drho_dS_dP, drho_dT_dP, is, npts)
1196 else
11970 do i=is,ie
11980 pres(i) = EOS%RL2_T2_to_Pa * pressure(i)
11990 Ta(i) = EOS%C_to_degC * T(i)
12000 Sa(i) = EOS%S_to_ppt * S(i)
1201 enddo
1202 call EOS%type%calculate_density_second_derivs_array(Ta, Sa, pres, drho_dS_dS, drho_dS_dT, &
12030 drho_dT_dT, drho_dS_dP, drho_dT_dP, is, npts)
1204 endif
1205
12060 rho_scale = EOS%kg_m3_to_R
12070 if (present(scale)) rho_scale = rho_scale * scale
12080 if (rho_scale /= 1.0) then ; do i=is,ie
12090 drho_dS_dS(i) = rho_scale * drho_dS_dS(i)
12100 drho_dS_dT(i) = rho_scale * drho_dS_dT(i)
12110 drho_dT_dT(i) = rho_scale * drho_dT_dT(i)
12120 drho_dS_dP(i) = rho_scale * drho_dS_dP(i)
12130 drho_dT_dP(i) = rho_scale * drho_dT_dP(i)
1214 enddo ; endif
1215
12160 if (EOS%RL2_T2_to_Pa /= 1.0) then ; do i=is,ie
12170 drho_dS_dP(i) = EOS%RL2_T2_to_Pa * drho_dS_dP(i)
12180 drho_dT_dP(i) = EOS%RL2_T2_to_Pa * drho_dT_dP(i)
1219 enddo ; endif
1220
12210 if (EOS%C_to_degC /= 1.0) then ; do i=is,ie
12220 drho_dS_dT(i) = EOS%C_to_degC * drho_dS_dT(i)
12230 drho_dT_dT(i) = EOS%C_to_degC**2 * drho_dT_dT(i)
12240 drho_dT_dP(i) = EOS%C_to_degC * drho_dT_dP(i)
1225 enddo ; endif
1226
12270 if (EOS%S_to_ppt /= 1.0) then ; do i=is,ie
12280 drho_dS_dS(i) = EOS%S_to_ppt**2 * drho_dS_dS(i)
12290 drho_dS_dT(i) = EOS%S_to_ppt * drho_dS_dT(i)
12300 drho_dS_dP(i) = EOS%S_to_ppt * drho_dS_dP(i)
1231 enddo ; endif
1232
12330end subroutine calculate_density_second_derivs_1d
1234
1235!> Calls the appropriate subroutine to calculate density second derivatives for 2D array inputs.
12360subroutine calculate_density_second_derivs_2d(T, S, pressure, drho_dS_dS, drho_dS_dT, drho_dT_dT, &
12370 drho_dS_dP, drho_dT_dP, EOS, dom, scale)
1238 real, intent(in) :: T(:,:) !< Potential temperature referenced to the surface [C ~> degC]
1239 real, intent(in) :: S(:,:) !< Salinity [S ~> ppt]
1240 real, intent(in) :: pressure(:,:) !< Pressure [R L2 T-2 ~> Pa]
1241 real, intent(inout) :: drho_dS_dS(:,:) !< Partial derivative of beta with respect to S
1242 !! [R S-2 ~> kg m-3 ppt-2]
1243 real, intent(inout) :: drho_dS_dT(:,:) !< Partial derivative of beta with respect to T
1244 !! [R S-1 C-1 ~> kg m-3 ppt-1 degC-1]
1245 real, intent(inout) :: drho_dT_dT(:,:) !< Partial derivative of alpha with respect to T
1246 !! [R C-2 ~> kg m-3 degC-2]
1247 real, intent(inout) :: drho_dS_dP(:,:) !< Partial derivative of beta with respect to pressure
1248 !! [T2 S-1 L-2 ~> kg m-3 ppt-1 Pa-1]
1249 real, intent(inout) :: drho_dT_dP(:,:) !< Partial derivative of alpha with respect to pressure
1250 !! [T2 C-1 L-2 ~> kg m-3 degC-1 Pa-1]
1251 type(EOS_type), intent(in) :: EOS !< Equation of state structure
1252 integer, optional, intent(in) :: dom(2,2) !< The domain of indices to work on
1253 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale density [various]
1254
1255 ! Local variables
12560 real :: Ta(size(T,1), size(T,2)) ! Temperature converted to [degC]
12570 real :: Sa(size(S,1), size(S,2)) ! Salinity converted to [ppt]
12580 real :: press(size(pressure,1), size(pressure,2)) ! Pressure converted to [Pa]
1259 real :: rho_scale ! A factor to convert density from kg m-3 to the desired units [R m3 kg-1 ~> 1]
1260 integer :: is, ie, js, je
1261 integer :: domain(2,2)
1262
12630 if (.not. allocated(EOS%type)) call MOM_error(FATAL, &
12640 "calculate_density_second_derivs_2d: EOS%form_of_EOS is not valid.")
1265
12660 if (present(dom)) then
12670 domain(:,:) = dom(:,:)
1268 else
12690 domain(1,:) = [1, size(drho_dT_dT, 1)]
12700 domain(2,:) = [1, size(drho_dT_dT, 2)]
1271 endif
12720 is = domain(1,1) ; ie = domain(1,2)
12730 js = domain(2,1) ; je = domain(2,2)
1274
12750 if (all([EOS%RL2_T2_to_Pa, EOS%C_to_degC, EOS%S_to_ppt] == 1.)) then
1276 call EOS%type%calculate_density_second_derivs_2d(T, S, pressure, &
12770 drho_dS_dS, drho_dS_dT, drho_dT_dT, drho_dS_dP, drho_dT_dP, domain)
1278 else
12790 press(is:ie, js:je) = EOS%RL2_T2_to_Pa * pressure(is:ie, js:je)
12800 Ta(is:ie, js:je) = EOS%C_to_degC * T(is:ie, js:je)
12810 Sa(is:ie, js:je) = EOS%S_to_ppt * S(is:ie, js:je)
1282 call EOS%type%calculate_density_second_derivs_2d(Ta, Sa, press, &
12830 drho_dS_dS, drho_dS_dT, drho_dT_dT, drho_dS_dP, drho_dT_dP, domain)
1284 endif
1285
12860 rho_scale = EOS%kg_m3_to_R
12870 if (present(scale)) rho_scale = rho_scale * scale
12880 if (rho_scale /= 1.0) then
12890 drho_dS_dS(is:ie, js:je) = rho_scale * drho_dS_dS(is:ie, js:je)
12900 drho_dS_dT(is:ie, js:je) = rho_scale * drho_dS_dT(is:ie, js:je)
12910 drho_dT_dT(is:ie, js:je) = rho_scale * drho_dT_dT(is:ie, js:je)
12920 drho_dS_dP(is:ie, js:je) = rho_scale * drho_dS_dP(is:ie, js:je)
12930 drho_dT_dP(is:ie, js:je) = rho_scale * drho_dT_dP(is:ie, js:je)
1294 endif
1295
12960 if (EOS%RL2_T2_to_Pa /= 1.0) then
12970 drho_dS_dP(is:ie, js:je) = EOS%RL2_T2_to_Pa * drho_dS_dP(is:ie, js:je)
12980 drho_dT_dP(is:ie, js:je) = EOS%RL2_T2_to_Pa * drho_dT_dP(is:ie, js:je)
1299 endif
1300
13010 if (EOS%C_to_degC /= 1.0) then
13020 drho_dS_dT(is:ie, js:je) = EOS%C_to_degC * drho_dS_dT(is:ie, js:je)
13030 drho_dT_dT(is:ie, js:je) = EOS%C_to_degC**2 * drho_dT_dT(is:ie, js:je)
13040 drho_dT_dP(is:ie, js:je) = EOS%C_to_degC * drho_dT_dP(is:ie, js:je)
1305 endif
1306
13070 if (EOS%S_to_ppt /= 1.0) then
13080 drho_dS_dS(is:ie, js:je) = EOS%S_to_ppt**2 * drho_dS_dS(is:ie, js:je)
13090 drho_dS_dT(is:ie, js:je) = EOS%S_to_ppt * drho_dS_dT(is:ie, js:je)
13100 drho_dS_dP(is:ie, js:je) = EOS%S_to_ppt * drho_dS_dP(is:ie, js:je)
1311 endif
1312
13130end subroutine calculate_density_second_derivs_2d
1314
1315!> Calls the appropriate subroutine to calculate density second derivatives for scalar inputs.
13160subroutine calculate_density_second_derivs_scalar(T, S, pressure, drho_dS_dS, drho_dS_dT, drho_dT_dT, &
1317 drho_dS_dP, drho_dT_dP, EOS, scale)
1318 real, intent(in) :: T !< Potential temperature referenced to the surface [C ~> degC]
1319 real, intent(in) :: S !< Salinity [S ~> ppt]
1320 real, intent(in) :: pressure !< Pressure [R L2 T-2 ~> Pa]
1321 real, intent(out) :: drho_dS_dS !< Partial derivative of beta with respect to S
1322 !! [R S-2 ~> kg m-3 ppt-2]
1323 real, intent(out) :: drho_dS_dT !< Partial derivative of beta with respect to T
1324 !! [R S-1 C-1 ~> kg m-3 ppt-1 degC-1]
1325 real, intent(out) :: drho_dT_dT !< Partial derivative of alpha with respect to T
1326 !! [R C-2 ~> kg m-3 degC-2]
1327 real, intent(out) :: drho_dS_dP !< Partial derivative of beta with respect to pressure
1328 !! [T2 S-1 L-2 ~> kg m-3 ppt-1 Pa-1]
1329 real, intent(out) :: drho_dT_dP !< Partial derivative of alpha with respect to pressure
1330 !! [T2 C-1 L-2 ~> kg m-3 degC-1 Pa-1]
1331 type(EOS_type), intent(in) :: EOS !< Equation of state structure
1332 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale density
1333 !! in combination with scaling stored in EOS [various]
1334 ! Local variables
1335 real :: rho_scale ! A factor to convert density from kg m-3 to the desired units [R m3 kg-1 ~> 1]
1336 real :: pres ! Pressure converted to [Pa]
1337 real :: Ta ! Temperature converted to [degC]
1338 real :: Sa ! Salinity converted to [ppt]
1339
13400 if (.not. allocated(EOS%type)) call MOM_error(FATAL, &
13410 "calculate_density_second_derivs: EOS%form_of_EOS is not valid.")
1342
13430 pres = EOS%RL2_T2_to_Pa*pressure
13440 Ta = EOS%C_to_degC * T
13450 Sa = EOS%S_to_ppt * S
1346
1347 call EOS%type%calculate_density_second_derivs_scalar(Ta, Sa, pres, drho_dS_dS, drho_dS_dT, &
13480 drho_dT_dT, drho_dS_dP, drho_dT_dP)
1349
13500 rho_scale = EOS%kg_m3_to_R
13510 if (present(scale)) rho_scale = rho_scale * scale
13520 if (rho_scale /= 1.0) then
13530 drho_dS_dS = rho_scale * drho_dS_dS
13540 drho_dS_dT = rho_scale * drho_dS_dT
13550 drho_dT_dT = rho_scale * drho_dT_dT
13560 drho_dS_dP = rho_scale * drho_dS_dP
13570 drho_dT_dP = rho_scale * drho_dT_dP
1358 endif
1359
13600 if (EOS%RL2_T2_to_Pa /= 1.0) then
13610 drho_dS_dP = EOS%RL2_T2_to_Pa * drho_dS_dP
13620 drho_dT_dP = EOS%RL2_T2_to_Pa * drho_dT_dP
1363 endif
1364
13650 if (EOS%C_to_degC /= 1.0) then
13660 drho_dS_dT = EOS%C_to_degC * drho_dS_dT
13670 drho_dT_dT = EOS%C_to_degC**2 * drho_dT_dT
13680 drho_dT_dP = EOS%C_to_degC * drho_dT_dP
1369 endif
1370
13710 if (EOS%S_to_ppt /= 1.0) then
13720 drho_dS_dS = EOS%S_to_ppt**2 * drho_dS_dS
13730 drho_dS_dT = EOS%S_to_ppt * drho_dS_dT
13740 drho_dS_dP = EOS%S_to_ppt * drho_dS_dP
1375 endif
1376
13770end subroutine calculate_density_second_derivs_scalar
1378
1379!> Calls the appropriate subroutine to calculate specific volume derivatives for an array.
138054000subroutine calculate_spec_vol_derivs_array(T, S, pressure, dSV_dT, dSV_dS, start, npts, EOS)
1381 real, dimension(:), intent(in) :: T !< Potential temperature referenced to the surface [degC]
1382 real, dimension(:), intent(in) :: S !< Salinity [ppt]
1383 real, dimension(:), intent(in) :: pressure !< Pressure [Pa]
1384 real, dimension(:), intent(inout) :: dSV_dT !< The partial derivative of specific volume with potential
1385 !! temperature [m3 kg-1 degC-1]
1386 real, dimension(:), intent(inout) :: dSV_dS !< The partial derivative of specific volume with salinity
1387 !! [m3 kg-1 ppt-1]
1388 integer, intent(in) :: start !< Starting index within the array
1389 integer, intent(in) :: npts !< The number of values to calculate
1390 type(EOS_type), intent(in) :: EOS !< Equation of state structure
1391
139254000 if (.not. allocated(EOS%type)) call MOM_error(FATAL, &
13930 "calculate_spec_vol_derivs_array: EOS%form_of_EOS is not valid.")
1394
139554000 call EOS%type%calculate_specvol_derivs_array(T, S, pressure, dSV_dT, dSV_dS, start, npts)
1396
139754000end subroutine calculate_spec_vol_derivs_array
1398
1399!> Calls the appropriate subroutine to calculate specific volume derivatives for 1-d array inputs,
1400!! potentially limiting the domain of indices that are worked on.
140154000subroutine calc_spec_vol_derivs_1d(T, S, pressure, dSV_dT, dSV_dS, EOS, dom, scale)
1402 real, dimension(:), intent(in) :: T !< Potential temperature referenced to the surface [C ~> degC]
1403 real, dimension(:), intent(in) :: S !< Salinity [S ~> ppt]
1404 real, dimension(:), intent(in) :: pressure !< Pressure [R L2 T-2 ~> Pa]
1405 real, dimension(:), intent(inout) :: dSV_dT !< The partial derivative of specific volume with potential
1406 !! temperature [R-1 C-1 ~> m3 kg-1 degC-1]
1407 real, dimension(:), intent(inout) :: dSV_dS !< The partial derivative of specific volume with salinity
1408 !! [R-1 S-1 ~> m3 kg-1 ppt-1]
1409 type(EOS_type), intent(in) :: EOS !< Equation of state structure
1410 integer, dimension(2), optional, intent(in) :: dom !< The domain of indices to work on, taking
1411 !! into account that arrays start at 1.
1412 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale specific
1413 !! volume in combination with scaling stored in EOS [various]
1414
1415 ! Local variables
1416108000 real, dimension(size(T)) :: pres ! Pressure converted to [Pa]
1417108000 real, dimension(size(T)) :: Ta ! Temperature converted to [degC]
1418108000 real, dimension(size(T)) :: Sa ! Salinity converted to [ppt]
1419 real :: spv_scale ! A factor to convert specific volume from m3 kg-1 to the desired units [kg R-1 m-3 ~> 1]
1420 real :: dSVdT_scale ! A factor to convert dSV_dT to the desired units [kg degC R-1 C-1 m-3 ~> 1]
1421 real :: dSVdS_scale ! A factor to convert dSV_dS to the desired units [kg ppt R-1 S-1 m-3 ~> 1]
1422 integer :: i, is, ie, npts
1423
142454000 if (present(dom)) then
142554000 is = dom(1) ; ie = dom(2) ; npts = 1 + ie - is
1426 else
14270 is = 1 ; ie = size(dSV_dT) ; npts = 1 + ie - is
1428 endif
1429
143054000 if ((EOS%RL2_T2_to_Pa == 1.0) .and. (EOS%C_to_degC == 1.0) .and. (EOS%S_to_ppt == 1.0)) then
143154000 call calculate_spec_vol_derivs_array(T, S, pressure, dSV_dT, dSV_dS, is, npts, EOS)
1432 else
14330 do i=is,ie
14340 pres(i) = EOS%RL2_T2_to_Pa * pressure(i)
14350 Ta(i) = EOS%C_to_degC * T(i)
14360 Sa(i) = EOS%S_to_ppt * S(i)
1437 enddo
14380 call calculate_spec_vol_derivs_array(Ta, Sa, pres, dSV_dT, dSV_dS, is, npts, EOS)
1439 endif
1440
144154000 spv_scale = EOS%R_to_kg_m3
144254000 if (present(scale)) spv_scale = spv_scale * scale
144354000 dSVdT_scale = spv_scale * EOS%C_to_degC
144454000 dSVdS_scale = spv_scale * EOS%S_to_ppt
144554000 if ((dSVdT_scale /= 1.0) .or. (dSVdS_scale /= 1.0)) then ; do i=is,ie
14460 dSV_dT(i) = dSVdT_scale * dSV_dT(i)
14470 dSV_dS(i) = dSVdS_scale * dSV_dS(i)
1448 enddo ; endif
1449
145054000end subroutine calc_spec_vol_derivs_1d
1451
1452
1453!> Calls the appropriate subroutine to calculate the density and compressibility for 1-D array
1454!! inputs. The inputs and outputs use dimensionally rescaled units.
14550subroutine calculate_compress_1d(T, S, pressure, rho, drho_dp, EOS, dom)
1456 real, dimension(:), intent(in) :: T !< Potential temperature referenced to the surface [C ~> degC]
1457 real, dimension(:), intent(in) :: S !< Salinity [S ~> ppt]
1458 real, dimension(:), intent(in) :: pressure !< Pressure [R L2 T-2 ~> Pa]
1459 real, dimension(:), intent(inout) :: rho !< In situ density [R ~> kg m-3]
1460 real, dimension(:), intent(inout) :: drho_dp !< The partial derivative of density with pressure
1461 !! (also the inverse of the square of sound speed)
1462 !! [T2 L-2 ~> s2 m-2]
1463 type(EOS_type), intent(in) :: EOS !< Equation of state structure
1464 integer, dimension(2), optional, intent(in) :: dom !< The domain of indices to work on, taking
1465 !! into account that arrays start at 1.
1466
1467 ! Local variables
14680 real, dimension(size(T)) :: pres ! Pressure converted to [Pa]
14690 real, dimension(size(T)) :: Ta ! Temperature converted to [degC]
14700 real, dimension(size(T)) :: Sa ! Salinity converted to [ppt]
1471 integer :: i, is, ie, npts
1472
14730 if (.not. allocated(EOS%type)) call MOM_error(FATAL, &
14740 "calculate_compress_1d: EOS%form_of_EOS is not valid.")
1475
14760 if (present(dom)) then
14770 is = dom(1) ; ie = dom(2) ; npts = 1 + ie - is
1478 else
14790 is = 1 ; ie = size(rho) ; npts = 1 + ie - is
1480 endif
1481
14820 do i=is,ie
14830 pres(i) = EOS%RL2_T2_to_Pa * pressure(i)
14840 Ta(i) = EOS%C_to_degC * T(i)
14850 Sa(i) = EOS%S_to_ppt * S(i)
1486 enddo
1487
14880 call EOS%type%calculate_compress_array(Ta, Sa, pres, rho, drho_dp, is, npts)
1489
14900 if (EOS%kg_m3_to_R /= 1.0) then ; do i=is,ie
14910 rho(i) = EOS%kg_m3_to_R * rho(i)
1492 enddo ; endif
14930 if (EOS%L_T_to_m_s /= 1.0) then ; do i=is,ie
14940 drho_dp(i) = EOS%L_T_to_m_s**2 * drho_dp(i)
1495 enddo ; endif
1496
14970end subroutine calculate_compress_1d
1498
1499!> Calculate density and compressibility for a scalar. This just promotes the scalar to an array
1500!! with a singleton dimension and calls calculate_compress_1d. The inputs and outputs use
1501!! dimensionally rescaled units.
15020subroutine calculate_compress_scalar(T, S, pressure, rho, drho_dp, EOS)
1503 real, intent(in) :: T !< Potential temperature referenced to the surface [C ~> degC]
1504 real, intent(in) :: S !< Salinity [S ~> ppt]
1505 real, intent(in) :: pressure !< Pressure [R L2 T-2 ~> Pa]
1506 real, intent(out) :: rho !< In situ density [R ~> kg m-3]
1507 real, intent(out) :: drho_dp !< The partial derivative of density with pressure (also the
1508 !! inverse of the square of sound speed) [T2 L-2 ~> s2 m-2]
1509 type(EOS_type), intent(in) :: EOS !< Equation of state structure
1510
1511 ! Local variables
1512 ! These arrays use the same units as their counterparts in calculate_compress_1d.
1513 real, dimension(1) :: pa ! Pressure in a size-1 1d array [R L2 T-2 ~> Pa]
1514 real, dimension(1) :: Ta ! Temperature in a size-1 1d array [C ~> degC]
1515 real, dimension(1) :: Sa ! Salinity in a size-1 1d array [S ~> ppt]
1516 real, dimension(1) :: rhoa ! In situ density in a size-1 1d array [R ~> kg m-3]
1517 real, dimension(1) :: drho_dpa ! The partial derivative of density with pressure (also the
1518 ! inverse of the square of sound speed) in a 1d array [T2 L-2 ~> s2 m-2]
1519
15200 Ta(1) = T ; Sa(1) = S ; pa(1) = pressure
1521
15220 call calculate_compress_1d(Ta, Sa, pa, rhoa, drho_dpa, EOS)
15230 rho = rhoa(1) ; drho_dp = drho_dpa(1)
1524
15250end subroutine calculate_compress_scalar
1526
1527!> Calls the appropriate subroutine to calculate the layer averaged specific volume either using
1528!! Boole's rule quadrature or analytical and nearly-analytical averages in pressure.
15290subroutine average_specific_vol(T, S, p_t, dp, SpV_avg, EOS, dom, scale)
1530 real, dimension(:), intent(in) :: T !< Potential temperature referenced to the surface [C ~> degC]
1531 real, dimension(:), intent(in) :: S !< Salinity [S ~> ppt]
1532 real, dimension(:), intent(in) :: p_t !< Pressure at the top of the layer [R L2 T-2 ~> Pa]
1533 real, dimension(:), intent(in) :: dp !< Pressure change in the layer [R L2 T-2 ~> Pa]
1534 real, dimension(:), intent(inout) :: SpV_avg !< The vertical average specific volume
1535 !! in the layer [R-1 ~> m3 kg-1]
1536 type(EOS_type), intent(in) :: EOS !< Equation of state structure
1537 integer, dimension(2), optional, intent(in) :: dom !< The domain of indices to work on, taking
1538 !! into account that arrays start at 1.
1539 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale
1540 !! output specific volume in combination with
1541 !! scaling stored in EOS [various]
1542
1543 ! Local variables
15440 real, dimension(size(T)) :: pres ! Layer-top pressure converted to [Pa]
15450 real, dimension(size(T)) :: dpres ! Pressure change converted to [Pa]
15460 real, dimension(size(T)) :: Ta ! Temperature converted to [degC]
15470 real, dimension(size(T)) :: Sa ! Salinity converted to [ppt]
1548 real :: T5(5) ! Temperatures at five quadrature points [C ~> degC]
1549 real :: S5(5) ! Salinities at five quadrature points [S ~> ppt]
1550 real :: p5(5) ! Pressures at five quadrature points [R L2 T-2 ~> Pa]
1551 real :: a5(5) ! Specific volumes at five quadrature points [R-1 ~> m3 kg-1]
1552 real, parameter :: C1_90 = 1.0/90.0 ! A rational constant [nondim]
1553 real :: spv_scale ! A factor to convert specific volume from m3 kg-1 to the desired units [kg m-3 R-1 ~> 1]
1554 integer :: i, n, is, ie, npts
1555
15560 if (present(dom)) then
15570 is = dom(1) ; ie = dom(2) ; npts = 1 + ie - is
1558 else
15590 is = 1 ; ie = size(T) ; npts = 1 + ie - is
1560 endif
1561
15620 if (EOS%EOS_quadrature) then
15630 do i=is,ie
15640 do n=1,5
15650 T5(n) = T(i) ; S5(n) = S(i)
15660 p5(n) = p_t(i) + 0.25*real(5-n)*dp(i)
1567 enddo
15680 call calculate_spec_vol(T5, S5, p5, a5, EOS)
1569
1570 ! Use Boole's rule to estimate the average specific volume.
15710 SpV_avg(i) = C1_90*(7.0*(a5(1)+a5(5)) + 32.0*(a5(2)+a5(4)) + 12.0*a5(3))
1572 enddo
15730 elseif ((EOS%RL2_T2_to_Pa == 1.0) .and. (EOS%C_to_degC == 1.0) .and. (EOS%S_to_ppt == 1.0)) then
15740 select case (EOS%form_of_EOS)
1575 case (EOS_LINEAR)
1576 call avg_spec_vol_linear(T, S, p_t, dp, SpV_avg, is, npts, EOS%Rho_T0_S0, &
15770 EOS%dRho_dT, EOS%dRho_dS, EOS%dRho_dp)
1578 case (EOS_WRIGHT)
15790 call avg_spec_vol_buggy_wright(T, S, p_t, dp, SpV_avg, is, npts)
1580 case (EOS_WRIGHT_FULL)
15810 call avg_spec_vol_wright_full(T, S, p_t, dp, SpV_avg, is, npts)
1582 case (EOS_WRIGHT_REDUCED)
15830 call avg_spec_vol_wright_red(T, S, p_t, dp, SpV_avg, is, npts)
1584 case default
15850 call MOM_error(FATAL, "No analytic average specific volume option is available with this EOS!")
1586 end select
1587 else
15880 do i=is,ie
15890 pres(i) = EOS%RL2_T2_to_Pa * p_t(i)
15900 dpres(i) = EOS%RL2_T2_to_Pa * dp(i)
15910 Ta(i) = EOS%C_to_degC * T(i)
15920 Sa(i) = EOS%S_to_ppt * S(i)
1593 enddo
15940 select case (EOS%form_of_EOS)
1595 case (EOS_LINEAR)
1596 call avg_spec_vol_linear(Ta, Sa, pres, dpres, SpV_avg, is, npts, EOS%Rho_T0_S0, &
15970 EOS%dRho_dT, EOS%dRho_dS, EOS%dRho_dp)
1598 case (EOS_WRIGHT)
15990 call avg_spec_vol_buggy_wright(Ta, Sa, pres, dpres, SpV_avg, is, npts)
1600 case (EOS_WRIGHT_FULL)
16010 call avg_spec_vol_wright_full(Ta, Sa, pres, dpres, SpV_avg, is, npts)
1602 case (EOS_WRIGHT_REDUCED)
16030 call avg_spec_vol_wright_red(Ta, Sa, pres, dpres, SpV_avg, is, npts)
1604 case default
16050 call MOM_error(FATAL, "No analytic average specific volume option is available with this EOS!")
1606 end select
1607 endif
1608
16090 spv_scale = EOS%R_to_kg_m3
16100 if (EOS%EOS_quadrature) spv_scale = 1.0
16110 if (present(scale)) spv_scale = spv_scale * scale
16120 if (spv_scale /= 1.0) then ; do i=is,ie
16130 SpV_avg(i) = spv_scale * SpV_avg(i)
1614 enddo ; endif
1615
16160end subroutine average_specific_vol
1617
1618!> Return the range of temperatures, salinities and pressures for which the equation of state that
1619!! is being used has been fitted to observations. Care should be taken when applying
1620!! this equation of state outside of its fit range.
16210subroutine EoS_fit_range(EOS, T_min, T_max, S_min, S_max, p_min, p_max)
1622 type(EOS_type), intent(in) :: EOS !< Equation of state structure
1623 real, optional, intent(out) :: T_min !< The minimum temperature over which this EoS is fitted [degC]
1624 real, optional, intent(out) :: T_max !< The maximum temperature over which this EoS is fitted [degC]
1625 real, optional, intent(out) :: S_min !< The minimum salinity over which this EoS is fitted [ppt]
1626 real, optional, intent(out) :: S_max !< The maximum salinity over which this EoS is fitted [ppt]
1627 real, optional, intent(out) :: p_min !< The minimum pressure over which this EoS is fitted [Pa]
1628 real, optional, intent(out) :: p_max !< The maximum pressure over which this EoS is fitted [Pa]
1629
16300 if (.not. allocated(EOS%type)) call MOM_error(FATAL, &
16310 "calculate_compress: EOS%form_of_EOS is not valid.")
1632
16330 call EOS%type%EoS_fit_range(T_min, T_max, S_min, S_max, p_min, p_max)
1634
16350end subroutine EoS_fit_range
1636
1637
1638!> This subroutine returns a two point integer array indicating the domain of i-indices
1639!! to work on in EOS calls based on information from a hor_index type
16401572function EOS_domain(HI, halo) result(EOSdom)
1641 type(hor_index_type), intent(in) :: HI !< The horizontal index structure
1642 integer, optional, intent(in) :: halo !< The halo size to work on; missing is equivalent to 0.
1643 integer, dimension(2) :: EOSdom !< The index domain that the EOS will work on, taking into account
1644 !! that the arrays inside the EOS routines will start at 1.
1645
1646 ! Local variables
1647 integer :: halo_sz
1648
16491572 halo_sz = 0 ; if (present(halo)) halo_sz = halo
1650
16511572 EOSdom(1) = HI%isc - (HI%isd-1) - halo_sz
16521572 EOSdom(2) = HI%iec - (HI%isd-1) + halo_sz
1653
16541572end function EOS_domain
1655
1656!> Calls the appropriate subroutine to calculate analytical and nearly-analytical
1657!! integrals in pressure across layers of geopotential anomalies, which are
1658!! required for calculating the finite-volume form pressure accelerations in a
1659!! non-Boussinesq model. There are essentially no free assumptions, apart from the
1660!! use of Boole's rule to do the horizontal integrals, and from a truncation in the
1661!! series for log(1-eps/1+eps) that assumes that |eps| < 0.34.
16620subroutine analytic_int_specific_vol_dp(T, S, p_t, p_b, alpha_ref, HI, EOS, &
16630 dza, intp_dza, intx_dza, inty_dza, halo_size, &
16640 bathyP, P_surf, dP_tiny, MassWghtInterp)
1665 type(hor_index_type), intent(in) :: HI !< The horizontal index structure
1666 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed), &
1667 intent(in) :: T !< Potential temperature referenced to the surface [C ~> degC]
1668 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed), &
1669 intent(in) :: S !< Salinity [S ~> ppt]
1670 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed), &
1671 intent(in) :: p_t !< Pressure at the top of the layer [R L2 T-2 ~> Pa]
1672 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed), &
1673 intent(in) :: p_b !< Pressure at the bottom of the layer [R L2 T-2 ~> Pa]
1674 real, intent(in) :: alpha_ref !< A mean specific volume that is subtracted out
1675 !! to reduce the magnitude of each of the integrals [R-1 ~> m3 kg-1]
1676 !! The calculation is mathematically identical with different values of
1677 !! alpha_ref, but this reduces the effects of roundoff.
1678 type(EOS_type), intent(in) :: EOS !< Equation of state structure
1679 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed), &
1680 intent(inout) :: dza !< The change in the geopotential anomaly across
1681 !! the layer [L2 T-2 ~> m2 s-2]
1682 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed), &
1683 optional, intent(inout) :: intp_dza !< The integral in pressure through the layer of the
1684 !! geopotential anomaly relative to the anomaly at the bottom of the
1685 !! layer [R L4 T-4 ~> Pa m2 s-2]
1686 real, dimension(HI%IsdB:HI%IedB,HI%jsd:HI%jed), &
1687 optional, intent(inout) :: intx_dza !< The integral in x of the difference between the
1688 !! geopotential anomaly at the top and bottom of the layer divided by
1689 !! the x grid spacing [L2 T-2 ~> m2 s-2]
1690 real, dimension(HI%isd:HI%ied,HI%JsdB:HI%JedB), &
1691 optional, intent(inout) :: inty_dza !< The integral in y of the difference between the
1692 !! geopotential anomaly at the top and bottom of the layer divided by
1693 !! the y grid spacing [L2 T-2 ~> m2 s-2]
1694 integer, optional, intent(in) :: halo_size !< The width of halo points on which to calculate dza.
1695 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed), &
1696 optional, intent(in) :: bathyP !< The pressure at the bathymetry [R L2 T-2 ~> Pa]
1697 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed), &
1698 optional, intent(in) :: P_surf !< The pressure at the ocean surface [R L2 T-2 ~> Pa]
1699 real, optional, intent(in) :: dP_tiny !< A miniscule pressure change with
1700 !! the same units as p_t [R L2 T-2 ~> Pa]
1701 integer, optional, intent(in) :: MassWghtInterp !< A flag indicating whether and how to use
1702 !! mass weighting to interpolate T/S in integrals
1703
1704 ! Local variables
1705 real :: dRdT_scale ! A factor to convert drho_dT to the desired units [R degC m3 C-1 kg-1 ~> 1]
1706 real :: dRdS_scale ! A factor to convert drho_dS to the desired units [R ppt m3 S-1 kg-1 ~> 1]
1707 real :: dRdp_scale ! A factor to convert drho_dp to the desired units [T-2 L2 s2 m-2 ~> 1]
1708
1709 ! We should never reach this point with quadrature. EOS_quadrature indicates that numerical
1710 ! integration be used instead of analytic. This is a safety check.
17110 if (EOS%EOS_quadrature) call MOM_error(FATAL, "EOS_quadrature is set!")
1712
17130 select case (EOS%form_of_EOS)
1714 case (EOS_LINEAR)
17150 dRdT_scale = EOS%kg_m3_to_R * EOS%C_to_degC
17160 dRdS_scale = EOS%kg_m3_to_R * EOS%S_to_ppt
17170 dRdp_scale = EOS%kg_m3_to_R * EOS%RL2_T2_to_Pa
1718 call int_spec_vol_dp_linear(T, S, p_t, p_b, alpha_ref, HI, EOS%kg_m3_to_R*EOS%Rho_T0_S0, &
1719 dRdT_scale*EOS%dRho_dT, dRdS_scale*EOS%dRho_dS, dRdp_scale*EOS%dRho_dp, &
1720 dza, intp_dza, intx_dza, inty_dza, halo_size, &
17210 bathyP, P_surf, dP_tiny, MassWghtInterp)
1722 case (EOS_WRIGHT)
1723 call int_spec_vol_dp_wright(T, S, p_t, p_b, alpha_ref, HI, dza, intp_dza, intx_dza, &
1724 inty_dza, halo_size, bathyP, P_surf, dP_tiny, MassWghtInterp, &
1725 SV_scale=EOS%R_to_kg_m3, pres_scale=EOS%RL2_T2_to_Pa, &
17260 temp_scale=EOS%C_to_degC, saln_scale=EOS%S_to_ppt)
1727 case (EOS_WRIGHT_FULL)
1728 call int_spec_vol_dp_wright_full(T, S, p_t, p_b, alpha_ref, HI, dza, intp_dza, intx_dza, &
1729 inty_dza, halo_size, bathyP, P_surf, dP_tiny, MassWghtInterp, &
1730 SV_scale=EOS%R_to_kg_m3, pres_scale=EOS%RL2_T2_to_Pa, &
17310 temp_scale=EOS%C_to_degC, saln_scale=EOS%S_to_ppt)
1732 case (EOS_WRIGHT_REDUCED)
1733 call int_spec_vol_dp_wright_red(T, S, p_t, p_b, alpha_ref, HI, dza, intp_dza, intx_dza, &
1734 inty_dza, halo_size, bathyP, P_surf, dP_tiny, MassWghtInterp, &
1735 SV_scale=EOS%R_to_kg_m3, pres_scale=EOS%RL2_T2_to_Pa, &
17360 temp_scale=EOS%C_to_degC, saln_scale=EOS%S_to_ppt)
1737 case default
17380 call MOM_error(FATAL, "No analytic integration option is available with this EOS!")
1739 end select
1740
17410end subroutine analytic_int_specific_vol_dp
1742
1743!> This subroutine calculates analytical and nearly-analytical integrals of
1744!! pressure anomalies across layers, which are required for calculating the
1745!! finite-volume form pressure accelerations in a Boussinesq model.
17460subroutine analytic_int_density_dz(T, S, z_t, z_b, rho_ref, rho_0, G_e, HI, EOS, dpa, &
17470 intz_dpa, intx_dpa, inty_dpa, bathyT, SSH, dz_neglect, MassWghtInterp, Z_0p)
1748 type(hor_index_type), intent(in) :: HI !< Ocean horizontal index structure
1749 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed), &
1750 intent(in) :: T !< Potential temperature referenced to the surface [C ~> degC]
1751 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed), &
1752 intent(in) :: S !< Salinity [S ~> ppt]
1753 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed), &
1754 intent(in) :: z_t !< Height at the top of the layer in depth units [Z ~> m]
1755 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed), &
1756 intent(in) :: z_b !< Height at the bottom of the layer [Z ~> m]
1757 real, intent(in) :: rho_ref !< A mean density [R ~> kg m-3], that is
1758 !! subtracted out to reduce the magnitude of each of the
1759 !! integrals.
1760 real, intent(in) :: rho_0 !< A density [R ~> kg m-3], that is used
1761 !! to calculate the pressure (as p~=-z*rho_0*G_e)
1762 !! used in the equation of state.
1763 real, intent(in) :: G_e !< The Earth's gravitational acceleration
1764 !! [L2 Z-1 T-2 ~> m s-2]
1765 type(EOS_type), intent(in) :: EOS !< Equation of state structure
1766 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed), &
1767 intent(inout) :: dpa !< The change in the pressure anomaly
1768 !! across the layer [R L2 T-2 ~> Pa]
1769 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed), &
1770 optional, intent(inout) :: intz_dpa !< The integral through the thickness of the
1771 !! layer of the pressure anomaly relative to the
1772 !! anomaly at the top of the layer [R L2 Z T-2 ~> Pa m]
1773 real, dimension(HI%IsdB:HI%IedB,HI%jsd:HI%jed), &
1774 optional, intent(inout) :: intx_dpa !< The integral in x of the difference between
1775 !! the pressure anomaly at the top and bottom of the
1776 !! layer divided by the x grid spacing [R L2 T-2 ~> Pa]
1777 real, dimension(HI%isd:HI%ied,HI%JsdB:HI%JedB), &
1778 optional, intent(inout) :: inty_dpa !< The integral in y of the difference between
1779 !! the pressure anomaly at the top and bottom of the
1780 !! layer divided by the y grid spacing [R L2 T-2 ~> Pa]
1781 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed), &
1782 optional, intent(in) :: bathyT !< The depth of the bathymetry [Z ~> m]
1783 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed), &
1784 optional, intent(in) :: SSH !< The sea surface height [Z ~> m]
1785 real, optional, intent(in) :: dz_neglect !< A miniscule thickness change [Z ~> m]
1786 integer, optional, intent(in) :: MassWghtInterp !< A flag indicating whether and how to use
1787 !! mass weighting to interpolate T/S in integrals
1788 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed), &
1789 optional, intent(in) :: Z_0p !< The height at which the pressure is 0 [Z ~> m]
1790
1791 ! Local variables
1792 real :: rho_scale ! A multiplicative factor by which to scale density from kg m-3 to the
1793 ! desired units [R m3 kg-1 ~> 1]
1794 real :: dRdT_scale ! A factor to convert drho_dT to the desired units [R degC m3 C-1 kg-1 ~> 1]
1795 real :: dRdS_scale ! A factor to convert drho_dS to the desired units [R ppt m3 S-1 kg-1 ~> 1]
1796 real :: dRdp_scale ! A factor to convert drho_dp to the desired units [T-2 L2 s2 m-2 ~> 1]
1797 real :: pres_scale ! A multiplicative factor to convert pressure into Pa [Pa T2 R-1 L-2 ~> 1]
1798
1799 ! We should never reach this point with quadrature. EOS_quadrature indicates that numerical
1800 ! integration be used instead of analytic. This is a safety check.
18010 if (EOS%EOS_quadrature) call MOM_error(FATAL, "EOS_quadrature is set!")
1802
18030 select case (EOS%form_of_EOS)
1804 case (EOS_LINEAR)
18050 rho_scale = EOS%kg_m3_to_R
18060 dRdT_scale = EOS%kg_m3_to_R * EOS%C_to_degC
18070 dRdS_scale = EOS%kg_m3_to_R * EOS%S_to_ppt
18080 dRdp_scale = EOS%kg_m3_to_R * EOS%RL2_T2_to_Pa
18090 if ((rho_scale /= 1.0) .or. (dRdT_scale /= 1.0) .or. (dRdS_scale /= 1.0) .or. (dRdp_scale /= 1.0)) then
1810 call int_density_dz_linear(T, S, z_t, z_b, rho_ref, rho_0, G_e, HI, rho_scale*EOS%Rho_T0_S0, &
1811 dRdT_scale*EOS%dRho_dT, dRdS_scale*EOS%dRho_dS, dRdp_scale*EOS%dRho_dp, &
18120 dpa, intz_dpa, intx_dpa, inty_dpa, bathyT, SSH, dz_neglect, MassWghtInterp, Z_0p=Z_0p)
1813 else
1814 call int_density_dz_linear(T, S, z_t, z_b, rho_ref, rho_0, G_e, HI, &
1815 EOS%Rho_T0_S0, EOS%dRho_dT, EOS%dRho_dS, EOS%dRho_dp, &
18160 dpa, intz_dpa, intx_dpa, inty_dpa, bathyT, SSH, dz_neglect, MassWghtInterp, Z_0p=Z_0p)
1817 endif
1818 case (EOS_WRIGHT)
18190 rho_scale = EOS%kg_m3_to_R
18200 pres_scale = EOS%RL2_T2_to_Pa
18210 if ((rho_scale /= 1.0) .or. (pres_scale /= 1.0) .or. (EOS%C_to_degC /= 1.0) .or. (EOS%S_to_ppt /= 1.0)) then
1822 call int_density_dz_wright(T, S, z_t, z_b, rho_ref, rho_0, G_e, HI, &
1823 dpa, intz_dpa, intx_dpa, inty_dpa, bathyT, SSH, &
1824 dz_neglect, MassWghtInterp, rho_scale, pres_scale, &
18250 temp_scale=EOS%C_to_degC, saln_scale=EOS%S_to_ppt, Z_0p=Z_0p)
1826 else
1827 call int_density_dz_wright(T, S, z_t, z_b, rho_ref, rho_0, G_e, HI, &
1828 dpa, intz_dpa, intx_dpa, inty_dpa, bathyT, SSH, &
18290 dz_neglect, MassWghtInterp, Z_0p=Z_0p)
1830 endif
1831 case (EOS_WRIGHT_FULL)
18320 rho_scale = EOS%kg_m3_to_R
18330 pres_scale = EOS%RL2_T2_to_Pa
18340 if ((rho_scale /= 1.0) .or. (pres_scale /= 1.0) .or. (EOS%C_to_degC /= 1.0) .or. (EOS%S_to_ppt /= 1.0)) then
1835 call int_density_dz_wright_full(T, S, z_t, z_b, rho_ref, rho_0, G_e, HI, &
1836 dpa, intz_dpa, intx_dpa, inty_dpa, bathyT, SSH, &
1837 dz_neglect, MassWghtInterp, rho_scale, pres_scale, &
18380 temp_scale=EOS%C_to_degC, saln_scale=EOS%S_to_ppt, Z_0p=Z_0p)
1839 else
1840 call int_density_dz_wright_full(T, S, z_t, z_b, rho_ref, rho_0, G_e, HI, &
1841 dpa, intz_dpa, intx_dpa, inty_dpa, bathyT, SSH, &
18420 dz_neglect, MassWghtInterp, Z_0p=Z_0p)
1843 endif
1844 case (EOS_WRIGHT_REDUCED)
18450 rho_scale = EOS%kg_m3_to_R
18460 pres_scale = EOS%RL2_T2_to_Pa
18470 if ((rho_scale /= 1.0) .or. (pres_scale /= 1.0) .or. (EOS%C_to_degC /= 1.0) .or. (EOS%S_to_ppt /= 1.0)) then
1848 call int_density_dz_wright_red(T, S, z_t, z_b, rho_ref, rho_0, G_e, HI, &
1849 dpa, intz_dpa, intx_dpa, inty_dpa, bathyT, SSH, &
1850 dz_neglect, MassWghtInterp, rho_scale, pres_scale, &
18510 temp_scale=EOS%C_to_degC, saln_scale=EOS%S_to_ppt, Z_0p=Z_0p)
1852 else
1853 call int_density_dz_wright_red(T, S, z_t, z_b, rho_ref, rho_0, G_e, HI, &
1854 dpa, intz_dpa, intx_dpa, inty_dpa, bathyT, SSH, &
18550 dz_neglect, MassWghtInterp, Z_0p=Z_0p)
1856 endif
1857 case default
18580 call MOM_error(FATAL, "No analytic integration option is available with this EOS!")
1859 end select
1860
18610end subroutine analytic_int_density_dz
1862
1863!> Returns true if the equation of state is compressible (i.e. has pressure dependence)
18640logical function query_compressible(EOS)
1865 type(EOS_type), intent(in) :: EOS !< Equation of state structure
1866
18670 query_compressible = EOS%compressible
18680end function query_compressible
1869
1870!> Returns the string identifying the equation of state with enumeration "id"
18710function get_EOS_name(id) result (eos_name)
1872 integer, optional, intent(in) :: id !< Enumerated ID
1873 character(:), allocatable :: eos_name !< The name of the EOS
1874
18750 select case (id)
1876 case (EOS_LINEAR)
18770 eos_name = EOS_LINEAR_STRING
1878 case (EOS_UNESCO)
18790 eos_name = EOS_UNESCO_STRING
1880 case (EOS_WRIGHT)
18810 eos_name = EOS_WRIGHT_STRING
1882 case (EOS_WRIGHT_REDUCED)
18830 eos_name = EOS_WRIGHT_RED_STRING
1884 case (EOS_WRIGHT_FULL)
18850 eos_name = EOS_WRIGHT_FULL_STRING
1886 case (EOS_TEOS10)
18870 eos_name = EOS_TEOS10_STRING
1888 case (EOS_ROQUET_RHO)
18890 eos_name = EOS_ROQUET_RHO_STRING
1890 case (EOS_ROQUET_SPV)
18910 eos_name = EOS_ROQUET_SPV_STRING
1892 case (EOS_JACKETT06)
18930 eos_name = EOS_JACKETT06_STRING
1894 case default
18950 call MOM_error(FATAL, "get_EOS_name: something went wrong internally - enumeration is not valid.")
1896 end select
1897
18980end function get_EOS_name
1899
1900!> Initializes EOS_type by allocating and reading parameters. The scaling factors in
1901!! US are stored in EOS for later use.
19021subroutine EOS_init(param_file, EOS, US, use_conT_absS)
1903 type(param_file_type), intent(in) :: param_file !< Parameter file structure
1904 type(EOS_type), intent(inout) :: EOS !< Equation of state structure
1905 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
1906 logical, intent(in), optional :: use_conT_absS !< True if the model is formulated for
1907 !! conservative temp and absolute salinity
1908 optional :: US
1909 ! Local variables
1910# include "version_variable.h"
1911 character(len=40) :: mdl = "MOM_EOS" ! This module's name.
1912 character(len=12) :: TFREEZE_DEFAULT ! The default freezing point expression
1913 character(len=40) :: tmpstr
1914 logical :: EOS_quad_default, EOS_TS_default
1915 real :: Rho_Tref_Sref ! Density at Tref degC and Sref ppt [kg m-3]
1916 real :: Tref ! Reference temperature [degC]
1917 real :: Sref ! Reference salinity [psu]
1918 real :: pref ! Reference pressure [Pa]
1919 real :: rho0 ! Density at T=0, S=0 and p=0 [kg m-3]
1920
1921 ! Read all relevant parameters and write them to the model log.
19221 call log_version(param_file, mdl, version, "")
1923
1924 call get_param(param_file, mdl, "EQN_OF_STATE", tmpstr, &
1925 "EQN_OF_STATE determines which ocean equation of state should be used. "//&
1926 'Currently, the valid choices are "LINEAR", "UNESCO", "JACKETT_MCD", '//&
1927 '"WRIGHT", "WRIGHT_REDUCED", "WRIGHT_FULL", "NEMO", "ROQUET_RHO", "ROQUET_SPV" '//&
19281 'and "TEOS10". This is only used if USE_EOS is true.', default=EOS_DEFAULT)
19291 select case (uppercase(tmpstr))
1930 case (EOS_LINEAR_STRING)
19310 call EOS_manual_init(EOS, form_of_EOS=EOS_LINEAR)
1932 case (EOS_UNESCO_STRING)
19330 call EOS_manual_init(EOS, form_of_EOS=EOS_UNESCO)
1934 case (EOS_JACKETT_STRING)
19350 call EOS_manual_init(EOS, form_of_EOS=EOS_UNESCO)
1936 case (EOS_WRIGHT_STRING)
19370 call EOS_manual_init(EOS, form_of_EOS=EOS_WRIGHT)
1938 case (EOS_WRIGHT_RED_STRING)
19390 call EOS_manual_init(EOS, form_of_EOS=EOS_WRIGHT_REDUCED)
1940 case (EOS_WRIGHT_FULL_STRING)
19410 call EOS_manual_init(EOS, form_of_EOS=EOS_WRIGHT_FULL)
1942 case (EOS_TEOS10_STRING)
19430 call EOS_manual_init(EOS, form_of_EOS=EOS_TEOS10)
1944 case (EOS_NEMO_STRING)
19450 call EOS_manual_init(EOS, form_of_EOS=EOS_ROQUET_RHO)
1946 case (EOS_ROQUET_RHO_STRING)
19471 call EOS_manual_init(EOS, form_of_EOS=EOS_ROQUET_RHO)
1948 case (EOS_ROQUET_SPV_STRING)
19490 call EOS_manual_init(EOS, form_of_EOS=EOS_ROQUET_SPV)
1950 case (EOS_JACKETT06_STRING)
19510 call EOS_manual_init(EOS, form_of_EOS=EOS_JACKETT06)
1952 case default
1953 call MOM_error(FATAL, "interpret_eos_selection: EQN_OF_STATE "//&
19541 trim(tmpstr) // " in input file is invalid.")
1955 end select
1956 call MOM_mesg('interpret_eos_selection: equation of state set to "' // &
19571 trim(tmpstr)//'"', 5)
1958
19591 if (EOS%form_of_EOS == EOS_LINEAR) then
1960 ! RHO(T,S) = RHO_REF + DRHO_DT*(T-T_REF) + DRHO_DS*(S-S_REF) + DRHO_DP*(P-P_REF)
1961 ! = RHO_REF - (DRHO_DT*T_REF + DRHO_DS*SREF + DRHO_DP*PREF) + (DRHO_DT*T + DRHO_DS*S + DRHO_DP*P)
1962 ! = RHO_T0_S0 + (DRHO_DT*T + DRHO_DS*S + DRHO_DP*P)
1963 call get_param(param_file, mdl, "RHO_REF_LINEAR_EOS", Rho_Tref_Sref, &
1964 "When EQN_OF_STATE="//trim(EOS_LINEAR_STRING)//", this is the density "//&
1965 "at T=T_REF_LINEAR_EOS, S=S_REF_LINEAR_EOS and p=P_REF_LINEAR_EOS", &
19660 units="kg m-3", default=1000.0, old_name="RHO_TREF_SREF")
1967 call get_param(param_file, mdl, "T_REF_LINEAR_EOS", Tref, &
1968 "When EQN_OF_STATE="//trim(EOS_LINEAR_STRING)//", this is the reference "//&
19690 "temperature.", units="degC", default=0.0, old_name="TREF")
1970 call get_param(param_file, mdl, "S_REF_LINEAR_EOS", Sref, &
1971 "When EQN_OF_STATE="//trim(EOS_LINEAR_STRING)//", this is the reference "//&
19720 "salinity.", units="psu", default=0.0, old_name="SREF")
1973 call get_param(param_file, mdl, "P_REF_LINEAR_EOS", pref, &
1974 "When EQN_OF_STATE="//trim(EOS_LINEAR_STRING)//", this is the reference "//&
19750 "pressure.", units="Pa", default=0.0)
1976 call get_param(param_file, mdl, "DRHO_DT", EOS%dRho_dT, &
1977 "When EQN_OF_STATE="//trim(EOS_LINEAR_STRING)//", this is "//&
1978 "the partial derivative of density with temperature.", &
19790 units="kg m-3 K-1", default=-0.2)
1980 call get_param(param_file, mdl, "DRHO_DS", EOS%dRho_dS, &
1981 "When EQN_OF_STATE="//trim(EOS_LINEAR_STRING)//", this is "//&
1982 "the partial derivative of density with salinity.", &
19830 units="kg m-3 ppt-1", default=0.8)
1984 call get_param(param_file, mdl, "DRHO_DP", EOS%dRho_dp, &
1985 "When EQN_OF_STATE="//trim(EOS_LINEAR_STRING)//", this is "//&
1986 "the partial derivative of density with pressure (the inverse of "//&
19870 "sound speed squared).", units="s2 m-2", default=0.0)
19880 rho0 = Rho_Tref_Sref - ((EOS%dRho_dT * Tref + EOS%dRho_dS * Sref) + EOS%dRho_dp * pref)
1989 call get_param(param_file, mdl, "RHO_T0_S0", EOS%Rho_T0_S0, &
1990 "When EQN_OF_STATE="//trim(EOS_LINEAR_STRING)//", this is the density "//&
1991 "at T=0, S=0 and p=0. If RHO_TO_SO is specified, RHO_REF_LINEAR_EOS, "//&
1992 "T_REF_LINEAR_EOS, S_REF_LINEAR_EOS and P_REF_LINEAR_EOS are not used.", &
19930 units="kg m-3", default=rho0)
19940 EOS%Compressible = (EOS%dRho_dp/=0.0)
1995 call EOS_manual_init(EOS, form_of_EOS=EOS_LINEAR, Rho_T0_S0=EOS%Rho_T0_S0, &
19960 dRho_dT=EOS%dRho_dT, dRho_dS=EOS%dRho_dS, dRho_dp=EOS%dRho_dp)
1997 endif
19981 if (EOS%form_of_EOS == EOS_WRIGHT) then
1999 call get_param(param_file, mdl, "USE_WRIGHT_2ND_DERIV_BUG", EOS%use_Wright_2nd_deriv_bug, &
2000 "If true, use a bug in the calculation of the second derivatives of density "//&
2001 "with temperature and with temperature and pressure that causes some terms "//&
20020 "to be only 2/3 of what they should be.", default=.false.)
20030 call EOS_manual_init(EOS, form_of_EOS=EOS_WRIGHT, use_Wright_2nd_deriv_bug=EOS%use_Wright_2nd_deriv_bug)
2004 endif
2005
20061 if (present(use_conT_absS)) then
20071 EOS%use_conT_absS = use_conT_absS
2008 else
20090 EOS%use_conT_absS = .false. ! Assuming it is not needed, it is set to false
2010 endif
2011
2012 EOS_quad_default = .not.((EOS%form_of_EOS == EOS_LINEAR) .or. &
2013 (EOS%form_of_EOS == EOS_WRIGHT) .or. &
2014 (EOS%form_of_EOS == EOS_WRIGHT_REDUCED) .or. &
20151 (EOS%form_of_EOS == EOS_WRIGHT_FULL))
2016 call get_param(param_file, mdl, "EOS_QUADRATURE", EOS%EOS_quadrature, &
2017 "If true, always use the generic (quadrature) code "//&
20181 "code for the integrals of density.", default=EOS_quad_default)
2019
20201 TFREEZE_DEFAULT = TFREEZE_LINEAR_STRING
20211 if ((EOS%form_of_EOS == EOS_TEOS10 .or. EOS%form_of_EOS == EOS_ROQUET_RHO .or. &
2022 EOS%form_of_EOS == EOS_ROQUET_SPV)) &
20231 TFREEZE_DEFAULT = TFREEZE_TEOS10_STRING
2024 call get_param(param_file, mdl, "TFREEZE_FORM", tmpstr, &
2025 "TFREEZE_FORM determines which expression should be "//&
2026 "used for the freezing point. Currently, the valid "//&
2027 'choices are "LINEAR", "MILLERO_78", "TEOS_POLY", "TEOS10"', &
20281 default=TFREEZE_DEFAULT)
20291 select case (uppercase(tmpstr))
2030 case (TFREEZE_LINEAR_STRING)
20310 EOS%form_of_TFreeze = TFREEZE_LINEAR
2032 case (TFREEZE_MILLERO_STRING)
20330 EOS%form_of_TFreeze = TFREEZE_MILLERO
2034 case (TFREEZE_TEOSPOLY_STRING)
20351 EOS%form_of_TFreeze = TFREEZE_TEOSPOLY
2036 case (TFREEZE_TEOS10_STRING)
20370 EOS%form_of_TFreeze = TFREEZE_TEOS10
2038 case default
2039 call MOM_error(FATAL, "interpret_eos_selection: TFREEZE_FORM "//&
20401 trim(tmpstr) // "in input file is invalid.")
2041 end select
2042
20431 if (EOS%form_of_TFreeze == TFREEZE_LINEAR) then
2044 call get_param(param_file, mdl, "TFREEZE_S0_P0",EOS%TFr_S0_P0, &
2045 "When TFREEZE_FORM="//trim(TFREEZE_LINEAR_STRING)//", "//&
2046 "this is the freezing potential temperature at "//&
20470 "S=0, P=0.", units="degC", default=0.0)
2048 call get_param(param_file, mdl, "DTFREEZE_DS",EOS%dTFr_dS, &
2049 "When TFREEZE_FORM="//trim(TFREEZE_LINEAR_STRING)//", "//&
2050 "this is the derivative of the freezing potential "//&
2051 "temperature with salinity.", &
20520 units="degC ppt-1", default=-0.054)
2053 call get_param(param_file, mdl, "DTFREEZE_DP",EOS%dTFr_dP, &
2054 "When TFREEZE_FORM="//trim(TFREEZE_LINEAR_STRING)//", "//&
2055 "this is the derivative of the freezing potential "//&
2056 "temperature with pressure.", &
20570 units="degC Pa-1", default=0.0)
2058 endif
2059
20601 if ((EOS%form_of_TFreeze==TFREEZE_TEOSPOLY) .or. (EOS%form_of_TFreeze==TFREEZE_TEOS10)) then
2061 ! Which default is appropriate for Millero?
20621 EOS_TS_default = .false.
2063 else
20640 EOS_TS_default = .true.
2065 endif
2066 call get_param(param_file, mdl, "TFREEZE_S_IS_PRACS", EOS%TFreeze_S_is_pracS, &
2067 "When True, the model will check if the model internal salinity is "//&
2068 "practical salinity. If the model uses absolute salinity, a "//&
20691 "conversion will be applied.", default=EOS_TS_default)
2070 call get_param(param_file, mdl, "TFREEZE_T_IS_POTT", EOS%TFreeze_T_is_potT, &
2071 "When True, the model will check if the model internal temperature is "//&
2072 "potential temperature. If the model uses conservative temperature, a "//&
20731 "conversion will be applied.", default=EOS_TS_default)
2074
2075 if ((EOS%form_of_EOS == EOS_TEOS10 .or. EOS%form_of_EOS == EOS_ROQUET_RHO .or. &
20761 EOS%form_of_EOS == EOS_ROQUET_SPV) .and. &
2077 .not.((EOS%form_of_TFreeze == TFREEZE_TEOS10) .or. (EOS%form_of_TFreeze == TFREEZE_TEOSPOLY)) ) then
2078 call MOM_error(WARNING, "interpret_eos_selection: EOS_TEOS10 or EOS_ROQUET_RHO or EOS_ROQUET_SPV "//&
20790 "should only be used along with TFREEZE_FORM = TFREEZE_TEOS10 or TFREEZE_TEOSPOLY.")
2080 endif
2081
2082 ! Unit conversions
20831 EOS%m_to_Z = 1. ; if (present(US)) EOS%m_to_Z = US%m_to_Z
20841 EOS%kg_m3_to_R = 1. ; if (present(US)) EOS%kg_m3_to_R = US%kg_m3_to_R
20851 EOS%R_to_kg_m3 = 1. ; if (present(US)) EOS%R_to_kg_m3 = US%R_to_kg_m3
20861 EOS%RL2_T2_to_Pa = 1. ; if (present(US)) EOS%RL2_T2_to_Pa = US%RL2_T2_to_Pa
20871 EOS%L_T_to_m_s = 1. ; if (present(US)) EOS%L_T_to_m_s = US%L_T_to_m_s
20881 EOS%degC_to_C = 1. ; if (present(US)) EOS%degC_to_C = US%degC_to_C
20891 EOS%C_to_degC = 1. ; if (present(US)) EOS%C_to_degC = US%C_to_degC
20901 EOS%ppt_to_S = 1. ; if (present(US)) EOS%ppt_to_S = US%ppt_to_S
20911 EOS%S_to_ppt = 1. ; if (present(US)) EOS%S_to_ppt = US%S_to_ppt
2092
20931end subroutine EOS_init
2094
2095!> Manually initialized an EOS type (intended for unit testing of routines which need a specific EOS)
20961subroutine EOS_manual_init(EOS, form_of_EOS, form_of_TFreeze, EOS_quadrature, Compressible, &
2097 Rho_T0_S0, drho_dT, dRho_dS, dRho_dp, TFr_S0_P0, dTFr_dS, dTFr_dp, &
2098 use_Wright_2nd_deriv_bug)
2099 type(EOS_type), intent(inout) :: EOS !< Equation of state structure
2100 integer, optional, intent(in) :: form_of_EOS !< A coded integer indicating the equation of state to use.
2101 integer, optional, intent(in) :: form_of_TFreeze !< A coded integer indicating the expression for
2102 !! the potential temperature of the freezing point.
2103 logical, optional, intent(in) :: EOS_quadrature !< If true, always use the generic (quadrature)
2104 !! code for the integrals of density.
2105 logical, optional, intent(in) :: Compressible !< If true, in situ density is a function of pressure.
2106 real , optional, intent(in) :: Rho_T0_S0 !< Density at T=0 degC and S=0 ppt [kg m-3]
2107 real , optional, intent(in) :: drho_dT !< Partial derivative of density with temperature
2108 !! in [kg m-3 degC-1]
2109 real , optional, intent(in) :: dRho_dS !< Partial derivative of density with salinity
2110 !! in [kg m-3 ppt-1]
2111 real , optional, intent(in) :: dRho_dp !< Partial derivative of density with pressure
2112 !! in [s2 m-2]
2113 real , optional, intent(in) :: TFr_S0_P0 !< The freezing potential temperature at S=0, P=0 [degC]
2114 real , optional, intent(in) :: dTFr_dS !< The derivative of freezing point with salinity
2115 !! in [degC ppt-1]
2116 real , optional, intent(in) :: dTFr_dp !< The derivative of freezing point with pressure
2117 !! in [degC Pa-1]
2118 logical, optional, intent(in) :: use_Wright_2nd_deriv_bug !< Allow the Wright 2nd deriv bug
2119
21201 if (present(form_of_EOS)) then
21211 EOS%form_of_EOS = form_of_EOS
21221 if (allocated(EOS%type)) deallocate(EOS%type) ! Needed during testing which re-initializes
21231 select case (EOS%form_of_EOS)
2124 case (EOS_LINEAR)
21250 allocate(linear_EOS :: EOS%type)
2126 case (EOS_UNESCO)
21270 allocate(UNESCO_EOS :: EOS%type)
2128 case (EOS_WRIGHT)
21290 allocate(buggy_Wright_EOS :: EOS%type)
2130 case (EOS_WRIGHT_FULL)
21310 allocate(Wright_full_EOS :: EOS%type)
2132 case (EOS_WRIGHT_REDUCED)
21330 allocate(Wright_red_EOS :: EOS%type)
2134 case (EOS_JACKETT06)
21350 allocate(Jackett06_EOS :: EOS%type)
2136 case (EOS_TEOS10)
21370 allocate(TEOS10_EOS :: EOS%type)
2138 case (EOS_ROQUET_RHO)
21391 allocate(Roquet_rho_EOS :: EOS%type)
2140 case (EOS_ROQUET_SPV)
21411 allocate(Roquet_SpV_EOS :: EOS%type)
2142 end select
2143 select type (t => EOS%type)
2144 type is (linear_EOS)
21450 call t%set_params_linear(Rho_T0_S0, dRho_dT, dRho_dS, dRho_dp)
2146 type is (buggy_Wright_EOS)
21470 call t%set_params_buggy_Wright(use_Wright_2nd_deriv_bug)
2148 end select
2149 endif
21501 if (present(form_of_TFreeze)) EOS%form_of_TFreeze = form_of_TFreeze
21511 if (present(EOS_quadrature )) EOS%EOS_quadrature = EOS_quadrature
21521 if (present(Compressible )) EOS%Compressible = Compressible
21531 if (present(Rho_T0_S0 )) EOS%Rho_T0_S0 = Rho_T0_S0
21541 if (present(drho_dT )) EOS%drho_dT = drho_dT
21551 if (present(dRho_dS )) EOS%dRho_dS = dRho_dS
21561 if (present(dRho_dp )) EOS%dRho_dp = dRho_dp
21571 if (present(TFr_S0_P0 )) EOS%TFr_S0_P0 = TFr_S0_P0
21581 if (present(dTFr_dS )) EOS%dTFr_dS = dTFr_dS
21591 if (present(dTFr_dp )) EOS%dTFr_dp = dTFr_dp
21601 if (present(use_Wright_2nd_deriv_bug)) EOS%use_Wright_2nd_deriv_bug = use_Wright_2nd_deriv_bug
2161
21621end subroutine EOS_manual_init
2163
2164! !> Set equation of state structure (EOS) to linear with given coefficients
2165! !!
2166! !! \note This routine is primarily for testing and allows a local copy of the
2167! !! EOS_type (EOS argument) to be set to use the linear equation of state
2168! !! independent from the rest of the model.
2169! subroutine EOS_use_linear(Rho_T0_S0, dRho_dT, dRho_dS, EOS, use_quadrature)
2170! real, intent(in) :: Rho_T0_S0 !< Density at T=0 degC and S=0 ppt [kg m-3]
2171! real, intent(in) :: dRho_dT !< Partial derivative of density with temperature [kg m-3 degC-1]
2172! real, intent(in) :: dRho_dS !< Partial derivative of density with salinity [kg m-3 ppt-1]
2173! logical, optional, intent(in) :: use_quadrature !< If true, always use the generic (quadrature)
2174! !! code for the integrals of density.
2175! type(EOS_type), intent(inout) :: EOS !< Equation of state structure
2176
2177! call EOS_manual_init(EOS, form_of_EOS=EOS_LINEAR, Rho_T0_S0=Rho_T0_S0, dRho_dT=dRho_dT, dRho_dS=dRho_dS)
2178! EOS%Compressible = .false.
2179! EOS%EOS_quadrature = .false.
2180! if (present(use_quadrature)) EOS%EOS_quadrature = use_quadrature
2181
2182! end subroutine EOS_use_linear
2183
2184!> Convert T&S to Absolute Salinity and Conservative Temperature if using TEOS10
21850subroutine convert_temp_salt_for_TEOS10(T, S, HI, kd, mask_z, EOS)
2186 integer, intent(in) :: kd !< The number of layers to work on
2187 type(hor_index_type), intent(in) :: HI !< The horizontal index structure
2188 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed,kd), &
2189 intent(inout) :: T !< Potential temperature referenced to the surface [C ~> degC]
2190 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed,kd), &
2191 intent(inout) :: S !< Salinity [S ~> ppt]
2192 real, dimension(HI%isd:HI%ied,HI%jsd:HI%jed,kd), &
2193 intent(in) :: mask_z !< 3d mask regulating which points to convert [nondim]
2194 type(EOS_type), intent(in) :: EOS !< Equation of state structure
2195
2196 real, parameter :: Sref_Sprac = (35.16504/35.0) ! The TEOS 10 conversion factor to go from
2197 ! practical salinity to reference salinity [PSU ppt-1]
2198 integer :: i, j, k
2199
22000 if ((EOS%form_of_EOS /= EOS_TEOS10) .and. (EOS%form_of_EOS /= EOS_ROQUET_RHO) .and. &
22010 (EOS%form_of_EOS /= EOS_ROQUET_SPV)) return
2202
22030 do k=1,kd ; do j=HI%jsc,HI%jec ; do i=HI%isc,HI%iec
22040 if (mask_z(i,j,k) >= 1.0) then
22050 S(i,j,k) = Sref_Sprac * S(i,j,k)
22060 T(i,j,k) = EOS%degC_to_C*poTemp_to_consTemp(EOS%C_to_degC*T(i,j,k), EOS%S_to_ppt*S(i,j,k))
2207 endif
2208 enddo ; enddo ; enddo
2209end subroutine convert_temp_salt_for_TEOS10
2210
2211
2212!> Converts an array of conservative temperatures to potential temperatures. The input arguments
2213!! use the dimensionally rescaling as specified within the EOS type. The output potential
2214!! temperature uses this same scaling, but this can be replaced by the factor given by scale.
22150subroutine cons_temp_to_pot_temp(T, S, poTemp, EOS, dom, scale)
2216 real, dimension(:), intent(in) :: T !< Conservative temperature [C ~> degC]
2217 real, dimension(:), intent(in) :: S !< Absolute salinity [S ~> ppt]
2218 real, dimension(:), intent(inout) :: poTemp !< The potential temperature with a reference pressure
2219 !! of 0 Pa, [C ~> degC]
2220 type(EOS_type), intent(in) :: EOS !< Equation of state structure
2221 integer, dimension(2), optional, intent(in) :: dom !< The domain of indices to work on, taking
2222 !! into account that arrays start at 1.
2223 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale the output
2224 !! potential temperature in place of with scaling stored
2225 !! in EOS. A value of 1.0 returns temperatures in [degC],
2226 !! while the default is equivalent to EOS%degC_to_C.
2227
2228 ! Local variables
22290 real, dimension(size(T)) :: Ta ! Temperature converted to [degC]
22300 real, dimension(size(S)) :: Sa ! Salinity converted to [ppt]
2231 real :: T_scale ! A factor to convert potential temperature from degC to the desired units [C degC-1 ~> 1]
2232 integer :: i, is, ie
2233
22340 if (present(dom)) then
22350 is = dom(1) ; ie = dom(2)
2236 else
22370 is = 1 ; ie = size(T)
2238 endif
2239
22400 if ((EOS%C_to_degC == 1.0) .and. (EOS%S_to_ppt == 1.0)) then
22410 poTemp(is:ie) = consTemp_to_poTemp(T(is:ie), S(is:ie))
2242 else
22430 do i=is,ie
22440 Ta(i) = EOS%C_to_degC * T(i)
22450 Sa(i) = EOS%S_to_ppt * S(i)
2246 enddo
22470 poTemp(is:ie) = consTemp_to_poTemp(Ta(is:ie), Sa(is:ie))
2248 endif
2249
22500 T_scale = EOS%degC_to_C
22510 if (present(scale)) T_scale = scale
22520 if (T_scale /= 1.0) then ; do i=is,ie
22530 poTemp(i) = T_scale * poTemp(i)
2254 enddo ; endif
2255
22560end subroutine cons_temp_to_pot_temp
2257
2258
2259!> Converts an array of potential temperatures to conservative temperatures. The input arguments
2260!! use the dimensionally rescaling as specified within the EOS type. The output potential
2261!! temperature uses this same scaling, but this can be replaced by the factor given by scale.
22620subroutine pot_temp_to_cons_temp(T, S, consTemp, EOS, dom, scale)
2263 real, dimension(:), intent(in) :: T !< Potential temperature [C ~> degC]
2264 real, dimension(:), intent(in) :: S !< Absolute salinity [S ~> ppt]
2265 real, dimension(:), intent(inout) :: consTemp !< The conservative temperature [C ~> degC]
2266 type(EOS_type), intent(in) :: EOS !< Equation of state structure
2267 integer, dimension(2), optional, intent(in) :: dom !< The domain of indices to work on, taking
2268 !! into account that arrays start at 1.
2269 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale the output
2270 !! potential temperature in place of with scaling stored
2271 !! in EOS. A value of 1.0 returns temperatures in [degC],
2272 !! while the default is equivalent to EOS%degC_to_C.
2273
2274 ! Local variables
22750 real, dimension(size(T)) :: Tp ! Potential temperature converted to [degC]
22760 real, dimension(size(S)) :: Sa ! Absolute salinity converted to [ppt]
2277 real :: T_scale ! A factor to convert potential temperature from degC to the desired units [C degC-1 ~> 1]
2278 integer :: i, is, ie
2279
22800 if (present(dom)) then
22810 is = dom(1) ; ie = dom(2)
2282 else
22830 is = 1 ; ie = size(T)
2284 endif
2285
2286
22870 if ((EOS%C_to_degC == 1.0) .and. (EOS%S_to_ppt == 1.0)) then
22880 consTemp(is:ie) = poTemp_to_consTemp(T(is:ie), S(is:ie))
2289 else
22900 do i=is,ie
22910 Tp(i) = EOS%C_to_degC * T(i)
22920 Sa(i) = EOS%S_to_ppt * S(i)
2293 enddo
22940 consTemp(is:ie) = poTemp_to_consTemp(Tp(is:ie), Sa(is:ie))
2295 endif
2296
22970 T_scale = EOS%degC_to_C
22980 if (present(scale)) T_scale = scale
22990 if (T_scale /= 1.0) then ; do i=is,ie
23000 consTemp(i) = T_scale * consTemp(i)
2301 enddo ; endif
2302
23030end subroutine pot_temp_to_cons_temp
2304
2305
2306!> Converts an array of absolute salinity to practical salinity. The input arguments
2307!! use the dimensionally rescaling as specified within the EOS type. The output potential
2308!! temperature uses this same scaling, but this can be replaced by the factor given by scale.
23090subroutine abs_saln_to_prac_saln(S, prSaln, EOS, dom, scale)
2310 real, dimension(:), intent(in) :: S !< Absolute salinity [S ~> ppt]
2311 real, dimension(:), intent(inout) :: prSaln !< Practical salinity [S ~> PSU]
2312 type(EOS_type), intent(in) :: EOS !< Equation of state structure
2313 integer, dimension(2), optional, intent(in) :: dom !< The domain of indices to work on, taking
2314 !! into account that arrays start at 1.
2315 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale the output
2316 !! practical salinities in place of with scaling stored
2317 !! in EOS. A value of 1.0 returns salinities in [PSU],
2318 !! while the default is equivalent to EOS%ppt_to_S.
2319
2320 ! Local variables
2321 real :: S_scale ! A factor to convert practical salinity from ppt to the desired units [S PSU-1 ~> 1]
2322 real, parameter :: Sprac_Sref = (35.0/35.16504) ! The TEOS 10 conversion factor to go from
2323 ! reference salinity to practical salinity [PSU ppt-1]
2324 integer :: i, is, ie
2325
23260 if (present(dom)) then
23270 is = dom(1) ; ie = dom(2)
2328 else
23290 is = 1 ; ie = size(S)
2330 endif
2331
23320 if (present(scale)) then
23330 S_scale = Sprac_Sref * scale
23340 do i=is,ie
23350 prSaln(i) = S_scale * S(i)
2336 enddo
2337 else
23380 do i=is,ie
23390 prSaln(i) = Sprac_Sref * S(i)
2340 enddo
2341 endif
2342
23430end subroutine abs_saln_to_prac_saln
2344
2345
2346!> Converts an array of absolute salinity to practical salinity. The input arguments
2347!! use the dimensionally rescaling as specified within the EOS type. The output potential
2348!! temperature uses this same scaling, but this can be replaced by the factor given by scale.
23490subroutine prac_saln_to_abs_saln(S, absSaln, EOS, dom, scale)
2350 real, dimension(:), intent(in) :: S !< Practical salinity [S ~> PSU]
2351 real, dimension(:), intent(inout) :: absSaln !< Absolute salinity [S ~> ppt]
2352 type(EOS_type), intent(in) :: EOS !< Equation of state structure
2353 integer, dimension(2), optional, intent(in) :: dom !< The domain of indices to work on, taking
2354 !! into account that arrays start at 1.
2355 real, optional, intent(in) :: scale !< A multiplicative factor by which to scale the output
2356 !! absolute salnities in place of with scaling stored
2357 !! in EOS. A value of 1.0 returns salinities in [ppt],
2358 !! while the default is equivalent to EOS%ppt_to_S.
2359
2360 ! Local variables
2361 real :: S_scale ! A factor to convert absolute salinity from ppt to the desired units [S ppt-1 ~> 1]
2362 real, parameter :: Sref_Sprac = (35.16504/35.0) ! The TEOS 10 conversion factor to go from
2363 ! practical salinity to reference salinity [PSU ppt-1]
2364 integer :: i, is, ie
2365
23660 if (present(dom)) then
23670 is = dom(1) ; ie = dom(2)
2368 else
23690 is = 1 ; ie = size(S)
2370 endif
2371
23720 if (present(scale)) then
23730 S_scale = Sref_Sprac * scale
23740 do i=is,ie
23750 absSaln(i) = S_scale * S(i)
2376 enddo
2377 else
23780 do i=is,ie
23790 absSaln(i) = Sref_Sprac * S(i)
2380 enddo
2381 endif
2382
23830end subroutine prac_saln_to_abs_saln
2384
2385
2386!> Return value of EOS_quadrature
23870logical function EOS_quadrature(EOS)
2388 type(EOS_type), intent(in) :: EOS !< Equation of state structure
2389
23900 EOS_quadrature = EOS%EOS_quadrature
2391
23920end function EOS_quadrature
2393
2394!> Runs unit tests for consistency on the equations of state.
2395!! This should only be called from a single/root thread.
2396!! It returns True if any test fails, otherwise it returns False.
23970logical function EOS_unit_tests(verbose)
2398 logical, intent(in) :: verbose !< If true, write results to stdout
2399 ! Local variables
24000 type(EOS_type) :: EOS_tmp
2401 logical :: fail
2402
24030 if (verbose) write(stdout,*) '==== MOM_EOS: EOS_unit_tests ===='
24040 EOS_unit_tests = .false. ! Normally return false
2405
24060 call EOS_manual_init(EOS_tmp, form_of_EOS=EOS_TEOS10)
2407 fail = test_TS_conversion_consistency(T_cons=9.989811727177308, S_abs=35.16504, &
24080 T_pot=10.0, S_prac=35.0, EOS=EOS_tmp, verbose=verbose)
24090 if (verbose .and. fail) call MOM_error(WARNING, "Some EOS variable conversions tests have failed.")
24100 EOS_unit_tests = EOS_unit_tests .or. fail
2411
24120 call EOS_manual_init(EOS_tmp, form_of_EOS=EOS_UNESCO)
2413 fail = test_EOS_consistency(25.0, 35.0, 1.0e7, EOS_tmp, verbose, "UNESCO", &
24140 rho_check=1027.54345796120*EOS_tmp%kg_m3_to_R)
24150 if (verbose .and. fail) call MOM_error(WARNING, "UNESCO EOS has failed some self-consistency tests.")
24160 EOS_unit_tests = EOS_unit_tests .or. fail
2417
24180 call EOS_manual_init(EOS_tmp, form_of_EOS=EOS_WRIGHT_FULL)
2419 fail = test_EOS_consistency(25.0, 35.0, 1.0e7, EOS_tmp, verbose, "WRIGHT_FULL", &
24200 rho_check=1027.55177447616*EOS_tmp%kg_m3_to_R, avg_Sv_check=.true.)
24210 if (verbose .and. fail) call MOM_error(WARNING, "WRIGHT_FULL EOS has failed some self-consistency tests.")
24220 EOS_unit_tests = EOS_unit_tests .or. fail
2423
24240 call EOS_manual_init(EOS_tmp, form_of_EOS=EOS_WRIGHT_REDUCED)
2425 fail = test_EOS_consistency(25.0, 35.0, 1.0e7, EOS_tmp, verbose, "WRIGHT_REDUCED", &
24260 rho_check=1027.54303596346*EOS_tmp%kg_m3_to_R, avg_Sv_check=.true.)
24270 if (verbose .and. fail) call MOM_error(WARNING, "WRIGHT_REDUCED EOS has failed some self-consistency tests.")
24280 EOS_unit_tests = EOS_unit_tests .or. fail
2429
2430 ! This test is deliberately outside of the fit range for WRIGHT_REDUCED, and it results in the expected warnings.
2431 ! call EOS_manual_init(EOS_tmp, form_of_EOS=EOS_WRIGHT_REDUCED)
2432 ! fail = test_EOS_consistency(25.0, 15.0, 1.0e7, EOS_tmp, verbose, "WRIGHT_REDUCED", &
2433 ! rho_check=1012.625699301455*EOS_tmp%kg_m3_to_R)
2434 ! if (verbose .and. fail) call MOM_error(WARNING, "WRIGHT_REDUCED EOS has failed some self-consistency tests.")
2435 ! EOS_unit_tests = EOS_unit_tests .or. fail
2436
24370 call EOS_manual_init(EOS_tmp, form_of_EOS=EOS_WRIGHT, use_Wright_2nd_deriv_bug=.true.)
2438 fail = test_EOS_consistency(25.0, 35.0, 1.0e7, EOS_tmp, verbose, "WRIGHT", &
24390 rho_check=1027.54303596346*EOS_tmp%kg_m3_to_R, avg_Sv_check=.true.)
2440 ! These last test is a known failure and since MPI is not necessarily initializaed when running these tests
2441 ! we need to avoid flagging the fails.
2442 !if (verbose .and. fail) call MOM_error(WARNING, "WRIGHT EOS has failed some self-consistency tests.")
2443 !EOS_unit_tests = EOS_unit_tests .or. fail
2444
24450 call EOS_manual_init(EOS_tmp, form_of_EOS=EOS_ROQUET_RHO)
2446 fail = test_EOS_consistency(25.0, 35.0, 1.0e7, EOS_tmp, verbose, "ROQUET_RHO", &
24470 rho_check=1027.42385663668*EOS_tmp%kg_m3_to_R)
24480 if (verbose .and. fail) call MOM_error(WARNING, "ROQUET_RHO EOS has failed some self-consistency tests.")
24490 EOS_unit_tests = EOS_unit_tests .or. fail
2450
24510 call EOS_manual_init(EOS_tmp, form_of_EOS=EOS_ROQUET_SPV)
2452 fail = test_EOS_consistency(25.0, 35.0, 1.0e7, EOS_tmp, verbose, "ROQUET_SPV", &
24530 rho_check=1027.42387475199*EOS_tmp%kg_m3_to_R)
24540 if (verbose .and. fail) call MOM_error(WARNING, "ROQUET_SPV EOS has failed some self-consistency tests.")
24550 EOS_unit_tests = EOS_unit_tests .or. fail
2456
24570 call EOS_manual_init(EOS_tmp, form_of_EOS=EOS_JACKETT06)
2458 fail = test_EOS_consistency(25.0, 35.0, 1.0e7, EOS_tmp, verbose, "JACKETT06", &
24590 rho_check=1027.539690758425*EOS_tmp%kg_m3_to_R)
24600 if (verbose .and. fail) call MOM_error(WARNING, "JACKETT06 EOS has failed some self-consistency tests.")
24610 EOS_unit_tests = EOS_unit_tests .or. fail
2462
2463 ! The TEOS10 equation of state is not passing the self consistency tests for dho_dS_dp due
2464 ! to a bug (a missing division by the square root of offset-salinity) on line 111 of
2465 ! pkg/GSW-Fortan/toolbox/gsw_specvol_second_derivatives.f90. This bug has been highlighted in an
2466 ! issue posted to the TEOS-10/GSW-Fortran page at github.com/TEOS-10/GSW-Fortran/issues/26, and
2467 ! it will be corrected by github.com/mom-ocean/GSW-Fortran/pull/1 .
24680 call EOS_manual_init(EOS_tmp, form_of_EOS=EOS_TEOS10)
2469 fail = test_EOS_consistency(25.0, 35.0, 1.0e7, EOS_tmp, verbose, "TEOS10", skip_2nd=.true., &
24700 rho_check=1027.42355961492*EOS_tmp%kg_m3_to_R)
24710 if (verbose .and. fail) call MOM_error(WARNING, "TEOS10 EOS has failed some self-consistency tests.")
24720 EOS_unit_tests = EOS_unit_tests .or. fail
2473
24740 call EOS_manual_init(EOS_tmp, form_of_EOS=EOS_ROQUET_RHO)
2475 fail = test_EOS_consistency(10.0, 30.0, 1.0e7, EOS_tmp, verbose, "ROQUET_RHO", &
24760 rho_check=1027.45140117152*EOS_tmp%kg_m3_to_R)
2477 ! The corresponding check value published by Roquet et al. (2015) is 1027.45140 [kg m-3].
24780 if (verbose .and. fail) call MOM_error(WARNING, "Roquet_rho EOS has failed some self-consistency tests.")
24790 EOS_unit_tests = EOS_unit_tests .or. fail
2480
24810 call EOS_manual_init(EOS_tmp, form_of_EOS=EOS_ROQUET_SPV)
2482 fail = test_EOS_consistency(10.0, 30.0, 1.0e7, EOS_tmp, verbose, "ROQUET_SPV", &
24830 spv_check=9.73282046614623e-04*EOS_tmp%R_to_kg_m3)
2484 ! The corresponding check value here published by Roquet et al. (2015) is 9.732819628e-04 [m3 kg-1],
2485 ! but the order of arithmetic there was not completely specified with parentheses.
24860 if (verbose .and. fail) call MOM_error(WARNING, "ROQUET_SPV EOS has failed some self-consistency tests.")
24870 EOS_unit_tests = EOS_unit_tests .or. fail
2488
24890 call EOS_manual_init(EOS_tmp, form_of_EOS=EOS_LINEAR, Rho_T0_S0=1000.0, drho_dT=-0.2, dRho_dS=0.8, dRho_dp=5.0e-7)
2490 fail = test_EOS_consistency(25.0, 35.0, 1.0e7, EOS_tmp, verbose, "LINEAR", &
24910 rho_check=1028.0*EOS_tmp%kg_m3_to_R, avg_Sv_check=.true.)
24920 if (verbose .and. fail) call MOM_error(WARNING, "LINEAR EOS has failed some self-consistency tests.")
24930 EOS_unit_tests = EOS_unit_tests .or. fail
2494
2495 ! Test the freezing point calculations
2496
2497 call EOS_manual_init(EOS_tmp, form_of_TFreeze=TFREEZE_LINEAR, TFr_S0_P0=0.0, dTFr_dS=-0.054, &
24980 dTFr_dP=-7.6e-8)
24990 fail = test_TFr_consistency(35.0, 1.0e7, EOS_tmp, verbose, "LINEAR", TFr_check=-2.65*EOS_tmp%degC_to_C)
25000 if (verbose .and. fail) call MOM_error(WARNING, "LINEAR TFr has failed some self-consistency tests.")
25010 EOS_unit_tests = EOS_unit_tests .or. fail
2502
25030 call EOS_manual_init(EOS_tmp, form_of_TFreeze=TFREEZE_MILLERO)
2504 fail = test_TFr_consistency(35.0, 1.0e7, EOS_tmp, verbose, "MILLERO_78", &
25050 TFr_check=-2.69730134114106*EOS_tmp%degC_to_C)
25060 if (verbose .and. fail) call MOM_error(WARNING, "MILLERO_78 TFr has failed some self-consistency tests.")
25070 EOS_unit_tests = EOS_unit_tests .or. fail
2508
25090 call EOS_manual_init(EOS_tmp, form_of_TFreeze=TFREEZE_TEOS10)
2510 fail = test_TFr_consistency(35.0, 1.0e7, EOS_tmp, verbose, "TEOS10", &
25110 TFr_check=-2.69099996992861*EOS_tmp%degC_to_C)
25120 if (verbose .and. fail) call MOM_error(WARNING, "TEOS10 TFr has failed some self-consistency tests.")
25130 EOS_unit_tests = EOS_unit_tests .or. fail
2514
25150 call EOS_manual_init(EOS_tmp, form_of_TFreeze=TFREEZE_TEOSPOLY)
2516 fail = test_TFr_consistency(35.0, 1.0e7, EOS_tmp, verbose, "TEOS_POLY", &
25170 TFr_check=-2.691165259327735*EOS_tmp%degC_to_C)
25180 if (verbose .and. fail) call MOM_error(WARNING, "TEOS_POLY TFr has failed some self-consistency tests.")
25190 EOS_unit_tests = EOS_unit_tests .or. fail
2520
25210 if (EOS_unit_tests) then
25220 call MOM_error(WARNING, "EOS_unit_tests: One or more EOS tests have failed!")
2523 else
25240 if (verbose) call MOM_mesg("EOS_unit_tests: All EOS consistency tests have passed.")
2525 endif
2526
25270end function EOS_unit_tests
2528
25290logical function test_TS_conversion_consistency(T_cons, S_abs, T_pot, S_prac, EOS, verbose) &
2530 result(inconsistent)
2531 real, intent(in) :: T_cons !< Conservative temperature [degC]
2532 real, intent(in) :: S_abs !< Absolute salinity [g kg-1]
2533 real, intent(in) :: T_pot !< Potential temperature [degC]
2534 real, intent(in) :: S_prac !< Practical salinity [PSU]
2535 type(EOS_type), intent(in) :: EOS !< Equation of state structure
2536 logical, intent(in) :: verbose !< If true, write results to stdout
2537
2538 ! Local variables
2539 real :: Sabs(1) ! Absolute or reference salinity [g kg-1]
2540 real :: Sprac(1) ! Practical salinity [PSU]
2541 real :: Stest(1) ! A converted salinity [ppt]
2542 real :: Tcons(1) ! Conservative temperature [degC]
2543 real :: Tpot(1) ! Potential temperature [degC]
2544 real :: Ttest(1) ! A converted temperature [degC]
2545 real :: Stol ! Roundoff error on a typical value of salinities [ppt]
2546 real :: Ttol ! Roundoff error on a typical value of temperatures [degC]
2547 logical :: test_OK ! True if a particular test is consistent.
2548 logical :: OK ! True if all checks so far are consistent.
2549
25500 OK = .true.
2551
2552 ! Copy scalar input values into the corresponding arrays
25530 Sabs(1) = S_abs ; Sprac(1) = S_prac ; Tcons(1) = T_cons ; Tpot(1) = T_pot
2554
2555 ! Set tolerances for the conversions.
25560 Ttol = 2.0 * 400.0*epsilon(Ttol)
25570 Stol = 35.0 * 400.0*epsilon(Stol)
2558
2559 ! Check that the converted salinities agree
25600 call abs_saln_to_prac_saln(Sabs, Stest, EOS)
25610 test_OK = (abs(Stest(1) - Sprac(1)) <= Stol)
25620 if (verbose) call write_check_msg("MOM6 Sprac", Stest(1), Sprac(1), Stol, test_OK)
25630 OK = OK .and. test_OK
2564
25650 call prac_saln_to_abs_saln(Sprac, Stest, EOS)
25660 test_OK = (abs(Stest(1) - Sabs(1)) <= Stol)
25670 if (verbose) call write_check_msg("MOM6 Sabs", Stest(1), Sabs(1), Stol, test_OK)
25680 OK = OK .and. test_OK
2569
25700 call cons_temp_to_pot_temp(Tcons, Sabs, Ttest, EOS)
25710 test_OK = (abs(Ttest(1) - Tpot(1)) <= Ttol)
25720 if (verbose) call write_check_msg("MOM6 Tpot", Ttest(1), Tpot(1), Ttol, test_OK)
25730 OK = OK .and. test_OK
2574
25750 call pot_temp_to_cons_temp(Tpot, Sabs, Ttest, EOS)
25760 test_OK = (abs(Ttest(1) - Tcons(1)) <= Ttol)
25770 if (verbose) call write_check_msg("MOM6 Tcons", Ttest(1), Tcons(1), Ttol, test_OK)
25780 OK = OK .and. test_OK
2579
25800 inconsistent = .not.OK
25810end function test_TS_conversion_consistency
2582
25830logical function test_TFr_consistency(S_test, p_test, EOS, verbose, EOS_name, TFr_check) &
2584 result(inconsistent)
2585 real, intent(in) :: S_test !< Salinity or absolute salinity [S ~> ppt]
2586 real, intent(in) :: p_test !< Pressure [R L2 T-2 ~> Pa]
2587 type(EOS_type), intent(in) :: EOS !< Equation of state structure
2588 logical, intent(in) :: verbose !< If true, write results to stdout
2589 character(len=*), intent(in) :: EOS_name !< A name used in error messages to describe the EoS
2590 real, optional, intent(in) :: TFr_check !< A check value for the Freezing point [C ~> degC]
2591
2592 ! Local variables
2593 real, dimension(-3:3,-3:3) :: S ! Salinities at the test value and perturbed points [S ~> ppt]
2594 real, dimension(-3:3,-3:3) :: P ! Pressures at the test value and perturbed points [R L2 T-2 ~> Pa]
2595 real, dimension(-3:3,-3:3,2) :: TFr ! Freezing point at the test value and perturbed points [C ~> degC]
2596 real :: dS ! Magnitude of salinity perturbations [S ~> ppt]
2597 real :: dp ! Magnitude of pressure perturbations [R L2 T-2 ~> Pa]
2598 ! real :: tol ! The nondimensional tolerance from roundoff [nondim]
2599 real :: TFr_tol ! Roundoff error on a typical value of TFreeze [C ~> degC]
2600 logical :: test_OK ! True if a particular test is consistent.
2601 logical :: OK ! True if all checks so far are consistent.
2602 integer :: i, j, n
2603
26040 OK = .true.
2605
26060 dS = 0.5*EOS%ppt_to_S ! Salinity perturbations [S ~> ppt]
26070 dp = 10.0e4 / EOS%RL2_T2_to_Pa ! Pressure perturbations [R L2 T-2 ~> Pa]
2608
2609 ! TEOS 10 requires a tolerance that is ~20 times larger than other freezing point
2610 ! expressions because it lacks parentheses.
26110 TFr_tol = 2.0*EOS%degC_to_C * 400.0*epsilon(TFr_tol)
2612
26130 do n=1,2
2614 ! Calculate density values with a wide enough stencil to estimate first and second derivatives
2615 ! with up to 6th order accuracy. Doing this twice with different sizes of perturbations allows
2616 ! the evaluation of whether the finite differences are converging to the calculated values at a
2617 ! rate that is consistent with the order of accuracy of the finite difference forms, and hence
2618 ! the consistency of the calculated values.
26190 do j=-3,3 ; do i=-3,3
26200 S(i,j) = max(S_test + n*dS*i, 0.0)
26210 p(i,j) = max(p_test + n*dp*j, 0.0)
2622 enddo ; enddo
26230 do j=-3,3
26240 call calculate_TFreeze(S(:,j), p(:,j), TFr(:,j,n), EOS)
2625 enddo
2626 enddo
2627
2628 ! Check that the freezing point agrees with the provided check value
26290 if (present(TFr_check)) then
26300 test_OK = (abs(TFr_check - TFr(0,0,1)) <= TFr_tol)
26310 OK = OK .and. test_OK
26320 if (verbose) call write_check_msg(trim(EOS_name)//" TFr", TFr(0,0,1), TFr_check, Tfr_tol, test_OK)
2633 endif
2634
26350 inconsistent = .not.OK
26360end function test_TFr_consistency
2637
2638!> Write a message indicating how well a value matches its check value.
26390subroutine write_check_msg(var_name, val, val_chk, val_tol, test_OK)
2640 character(len=*), intent(in) :: var_name !< The name of the variable being tested.
2641 real, intent(in) :: val !< The value being checked [various]
2642 real, intent(in) :: val_chk !< The value being checked [various]
2643 real, intent(in) :: val_tol !< The value being checked [various]
2644 logical, intent(in) :: test_OK !< True if the values are within their tolerance
2645
2646 character(len=200) :: mesg
2647
2648 write(mesg, '(ES24.16," vs. ",ES24.16,", diff=",ES12.4,", tol=",ES12.4)') &
26490 val, val_chk, val-val_chk, val_tol
26500 if (test_OK) then
26510 write(stdout,*) trim(var_name)//" agrees with its check value :"//trim(mesg)
2652 else
26530 write(stderr,*) trim(var_name)//" disagrees with its check value :"//trim(mesg)
2654 endif
26550end subroutine write_check_msg
2656
2657!> Test an equation of state for self-consistency and consistency with check values, returning false
2658!! if it is consistent by all tests, and true if it fails any test.
26590logical function test_EOS_consistency(T_test, S_test, p_test, EOS, verbose, &
2660 EOS_name, rho_check, spv_check, skip_2nd, avg_Sv_check) result(inconsistent)
2661 real, intent(in) :: T_test !< Potential temperature or conservative temperature [C ~> degC]
2662 real, intent(in) :: S_test !< Salinity or absolute salinity [S ~> ppt]
2663 real, intent(in) :: p_test !< Pressure [R L2 T-2 ~> Pa]
2664 type(EOS_type), intent(in) :: EOS !< Equation of state structure
2665 logical, intent(in) :: verbose !< If true, write results to stdout
2666 character(len=*), intent(in) :: EOS_name !< A name used in error messages to describe the EoS
2667 real, optional, intent(in) :: rho_check !< A check value for the density [R ~> kg m-3]
2668 real, optional, intent(in) :: spv_check !< A check value for the specific volume [R-1 ~> m3 kg-1]
2669 logical, optional, intent(in) :: skip_2nd !< If present and true, do not check the 2nd derivatives.
2670 logical, optional, intent(in) :: avg_Sv_check !< If present and true, compare analytical and numerical
2671 !! quadrature estimates of the layer-averaged specific volume.
2672
2673 ! Local variables
2674 real, dimension(-3:3,-3:3,-3:3) :: T ! Temperatures at the test value and perturbed points [C ~> degC]
2675 real, dimension(-3:3,-3:3,-3:3) :: S ! Salinities at the test value and perturbed points [S ~> ppt]
2676 real, dimension(-3:3,-3:3,-3:3) :: P ! Pressures at the test value and perturbed points [R L2 T-2 ~> Pa]
2677 real, dimension(-3:3,-3:3,-3:3,2) :: rho ! Densities relative to rho_ref at the test value and
2678 ! perturbed points [R ~> kg m-3]
2679 real, dimension(-3:3,-3:3,-3:3,2) :: spv ! Specific volumes relative to spv_ref at the test value and
2680 ! perturbed points [R-1 ~> m3 kg-1]
2681 real :: dT ! Magnitude of temperature perturbations [C ~> degC]
2682 real :: dS ! Magnitude of salinity perturbations [S ~> ppt]
2683 real :: dp ! Magnitude of pressure perturbations [R L2 T-2 ~> Pa]
2684 real :: rho_ref ! A reference density that is extracted for greater accuracy [R ~> kg m-3]
2685 real :: spv_ref ! A reference specific volume that is extracted for greater accuracy [R-1 ~> m3 kg-1]
2686 real :: rho_nooff ! Density with no reference offset [R ~> kg m-3]
2687 real :: spv_nooff ! Specific volume with no reference offset [R-1 ~> m3 kg-1]
2688 real :: drho_dT ! The partial derivative of density with potential
2689 ! temperature [R C-1 ~> kg m-3 degC-1]
2690 real :: drho_dS ! The partial derivative of density with salinity
2691 ! in [R S-1 ~> kg m-3 ppt-1]
2692 real :: drho_dp ! The partial derivative of density with pressure (also the
2693 ! inverse of the square of sound speed) [T2 L-2 ~> s2 m-2]
2694 real :: dSV_dT(1) ! The partial derivative of specific volume with potential
2695 ! temperature [R-1 C-1 ~> m3 kg-1 degC-1]
2696 real :: dSV_dS(1) ! The partial derivative of specific volume with salinity
2697 ! [R-1 S-1 ~> m3 kg-1 ppt-1]
2698 real :: SpV_avg_a(1) ! The pressure-averaged specific volume determined analytically [R-1 ~> m3 kg-1]
2699 real :: SpV_avg_q(1) ! The pressure-averaged specific volume determined via quadrature [R-1 ~> m3 kg-1]
2700 real :: drho_dS_dS ! Second derivative of density with respect to S [R S-2 ~> kg m-3 ppt-2]
2701 real :: drho_dS_dT ! Second derivative of density with respect to T and S [R S-1 C-1 ~> kg m-3 ppt-1 degC-1]
2702 real :: drho_dT_dT ! Second derivative of density with respect to T [R C-2 ~> kg m-3 degC-2]
2703 real :: drho_dS_dP ! Second derivative of density with respect to salinity and pressure
2704 ! [T2 S-1 L-2 ~> kg m-3 ppt-1 Pa-1]
2705 real :: drho_dT_dP ! Second derivative of density with respect to temperature and pressure
2706 ! [T2 C-1 L-2 ~> kg m-3 degC-1 Pa-1]
2707
2708 real :: drho_dT_fd(2) ! Two 6th order finite difference estimates of the partial derivative of density
2709 ! with potential temperature [R C-1 ~> kg m-3 degC-1]
2710 real :: drho_dS_fd(2) ! Two 6th order finite difference estimates of the partial derivative of density
2711 ! with salinity [R S-1 ~> kg m-3 ppt-1]
2712 real :: drho_dp_fd(2) ! Two 6th order finite difference estimates of the partial derivative of density
2713 ! with pressure (also the inverse of the square of sound speed) [T2 L-2 ~> s2 m-2]
2714 real :: dSV_dT_fd(2) ! Two 6th order finite difference estimates of the partial derivative of
2715 ! specific volume with potential temperature [R-1 C-1 ~> m3 kg-1 degC-1]
2716 real :: dSV_dS_fd(2) ! Two 6th order finite difference estimates of the partial derivative of
2717 ! specific volume with salinity [R-1 S-1 ~> m3 kg-1 ppt-1]
2718 real :: drho_dS_dS_fd(2) ! Two 6th order finite difference estimates of the second derivative of
2719 ! density with respect to salinity [R S-2 ~> kg m-3 ppt-2]
2720 real :: drho_dS_dT_fd(2) ! Two 6th order finite difference estimates of the second derivative of density
2721 ! with respect to temperature and salinity [R S-1 C-1 ~> kg m-3 ppt-1 degC-1]
2722 real :: drho_dT_dT_fd(2) ! Two 6th order finite difference estimates of the second derivative of
2723 ! density with respect to temperature [R C-2 ~> kg m-3 degC-2]
2724 real :: drho_dS_dP_fd(2) ! Two 6th order finite difference estimates of the second derivative of density
2725 ! with respect to salinity and pressure [T2 S-1 L-2 ~> kg m-3 ppt-1 Pa-1]
2726 real :: drho_dT_dP_fd(2) ! Two 6th order finite difference estimates of the second derivative of density
2727 ! with respect to temperature and pressure [T2 C-1 L-2 ~> kg m-3 degC-1 Pa-1]
2728 real :: rho_tmp ! A temporary copy of the situ density [R ~> kg m-3]
2729 real :: tol ! The nondimensional tolerance from roundoff [nondim]
2730 real :: r_tol ! Roundoff error on a typical value of density anomaly [R ~> kg m-3]
2731 real :: sv_tol ! Roundoff error on a typical value of specific volume anomaly [R-1 ~> m3 kg-1]
2732 real :: tol_here ! The tolerance for each check, in various units [various]
2733 real :: T_min, T_max ! The minimum and maximum temperature over which this EoS is fitted [degC]
2734 real :: S_min, S_max ! The minimum and maximum temperature over which this EoS is fitted [ppt]
2735 real :: p_min, p_max ! The minimum and maximum temperature over which this EoS is fitted [Pa]
2736 real :: count_fac ! A factor in the roundoff estimates based on the factors in the numerator and
2737 ! denominator in the finite difference derivative expression [nondim]
2738 real :: count_fac2 ! A factor in the roundoff estimates based on the factors in the numerator and
2739 ! denominator in the finite difference second derivative expression [nondim]
2740 character(len=200) :: mesg
27410 type(EOS_type) :: EOS_tmp
2742 logical :: test_OK ! True if a particular test is consistent.
2743 logical :: OK ! True if all checks so far are consistent.
2744 logical :: test_2nd ! If true, do tests on the 2nd derivative calculations
2745 logical :: test_avg_Sv ! If true, compare numerical and analytical estimates of the vertically
2746 ! averaged specific volume
2747 integer :: order ! The order of accuracy of the centered finite difference estimates (2, 4 or 6).
2748 integer :: i, j, k, n
2749
27500 test_2nd = .true. ; if (present(skip_2nd)) test_2nd = .not.skip_2nd
27510 test_avg_Sv = .false. ; if (present(avg_Sv_check)) test_avg_Sv = avg_Sv_check
2752
27530 dT = 0.1*EOS%degC_to_C ! Temperature perturbations [C ~> degC]
27540 dS = 0.5*EOS%ppt_to_S ! Salinity perturbations [S ~> ppt]
27550 dp = 10.0e4 / EOS%RL2_T2_to_Pa ! Pressure perturbations [R L2 T-2 ~> Pa]
2756
27570 r_tol = 50.0*EOS%kg_m3_to_R * 10.*epsilon(r_tol)
27580 sv_tol = 5.0e-5*EOS%R_to_kg_m3 * 10.*epsilon(sv_tol)
27590 rho_ref = 1000.0*EOS%kg_m3_to_R
27600 spv_ref = 1.0 / rho_ref
2761
27620 order = 4 ! This should be 2, 4 or 6.
2763
2764 ! Check whether the consistency test is being applied outside of the value range of this EoS.
27650 call EoS_fit_range(EOS, T_min, T_max, S_min, S_max, p_min, p_max)
27660 if ((T_test < T_min) .or. (T_test > T_max)) then
27670 write(mesg, '(ES12.4," [degC] which is outside of the fit range of ",ES12.4," to ",ES12.4)') T_test, T_min, T_max
27680 call MOM_error(WARNING, trim(EOS_name)//" is being evaluated at a temperature of "//trim(mesg))
2769 endif
27700 if ((S_test < S_min) .or. (S_test > S_max)) then
27710 write(mesg, '(ES12.4," [ppt] which is outside of the fit range of ",ES12.4," to ",ES12.4)') S_test, S_min, S_max
27720 call MOM_error(WARNING, trim(EOS_name)//" is being evaluated at a salinity of "//trim(mesg))
2773 endif
27740 if ((p_test < p_min) .or. (p_test > p_max)) then
27750 write(mesg, '(ES12.4," [Pa] which is outside of the fit range of ",ES12.4," to ",ES12.4)') p_test, p_min, p_max
27760 call MOM_error(WARNING, trim(EOS_name)//" is being evaluated at a pressure of "//trim(mesg))
2777 endif
2778
27790 do n=1,2
2780 ! Calculate density values with a wide enough stencil to estimate first and second derivatives
2781 ! with up to 6th order accuracy. Doing this twice with different sizes of perturbations allows
2782 ! the evaluation of whether the finite differences are converging to the calculated values at a
2783 ! rate that is consistent with the order of accuracy of the finite difference forms, and hence
2784 ! the consistency of the calculated values.
27850 do k=-3,3 ; do j=-3,3 ; do i=-3,3
27860 T(i,j,k) = T_test + n*dT*i
27870 S(i,j,k) = S_test + n*dS*j
27880 p(i,j,k) = p_test + n*dp*k
2789 enddo ; enddo ; enddo
27900 do k=-3,3 ; do j=-3,3
27910 call calculate_density(T(:,j,k), S(:,j,k), p(:,j,k), rho(:,j,k,n), EOS, rho_ref=rho_ref)
27920 call calculate_spec_vol(T(:,j,k), S(:,j,k), p(:,j,k), spv(:,j,k,n), EOS, spv_ref=spv_ref)
2793 enddo ; enddo
2794
27950 drho_dT_fd(n) = first_deriv(rho(:,0,0,n), n*dT, order)
27960 drho_dS_fd(n) = first_deriv(rho(0,:,0,n), n*dS, order)
27970 drho_dp_fd(n) = first_deriv(rho(0,0,:,n), n*dp, order)
27980 dSV_dT_fd(n) = first_deriv(spv(:,0,0,n), n*dT, order)
27990 dSV_dS_fd(n) = first_deriv(spv(0,:,0,n), n*dS, order)
28000 if (test_2nd) then
28010 drho_dT_dT_fd(n) = second_deriv(rho(:,0,0,n), n*dT, order)
28020 drho_dS_dS_fd(n) = second_deriv(rho(0,:,0,n), n*dS, order)
28030 drho_dS_dT_fd(n) = derivs_2d(rho(:,:,0,n), n**2*dT*dS, order)
28040 drho_dT_dP_fd(n) = derivs_2d(rho(:,0,:,n), n**2*dT*dP, order)
28050 drho_dS_dP_fd(n) = derivs_2d(rho(0,:,:,n), n**2*dS*dP, order)
2806 endif
2807 enddo
2808
28090 call calculate_density_derivs(T(0,0,0), S(0,0,0), p(0,0,0), drho_dT, drho_dS, EOS)
2810 ! The first indices here are "0:0" because there is no scalar form of calculate_specific_vol_derivs.
28110 call calculate_specific_vol_derivs(T(0:0,0,0), S(0:0,0,0), p(0:0,0,0), dSV_dT, dSV_dS, EOS)
28120 if (test_2nd) &
2813 call calculate_density_second_derivs(T(0,0,0), S(0,0,0), p(0,0,0), &
28140 drho_dS_dS, drho_dS_dT, drho_dT_dT, drho_dS_dP, drho_dT_dP, EOS)
28150 call calculate_compress(T(0,0,0), S(0,0,0), p(0,0,0), rho_tmp, drho_dp, EOS)
2816
28170 if (test_avg_Sv) then
28180 EOS_tmp = EOS
28190 call EOS_manual_init(EOS_tmp, EOS_quadrature=.false.)
28200 call average_specific_vol(T(0:0,0,0), S(0:0,0,0), p(0:0,0,0), p(0:0,0,0), SpV_avg_a, EOS_tmp)
28210 call EOS_manual_init(EOS_tmp, EOS_quadrature=.true.)
28220 call average_specific_vol(T(0:0,0,0), S(0:0,0,0), p(0:0,0,0), p(0:0,0,0), SpV_avg_q, EOS_tmp)
2823 endif
2824
28250 OK = .true.
2826
28270 tol = 1000.0*epsilon(tol)
2828
2829 ! Check that the density agrees with the provided check value
28300 if (present(rho_check)) then
28310 test_OK = (abs(rho_check - (rho_ref + rho(0,0,0,1))) < tol*(rho_ref + rho(0,0,0,1)))
28320 OK = OK .and. test_OK
28330 if (verbose) &
28340 call write_check_msg(trim(EOS_name)//" rho", rho_ref+rho(0,0,0,1), rho_check, tol*rho(0,0,0,1), test_OK)
2835 endif
2836
2837 ! Check that the specific volume agrees with the provided check value or the inverse of density
28380 if (present(spv_check)) then
28390 test_OK = (abs(spv_check - (spv_ref + spv(0,0,0,1))) < tol*abs(spv_ref + spv(0,0,0,1)))
28400 if (verbose) &
28410 call write_check_msg(trim(EOS_name)//" spv", spv_ref+spv(0,0,0,1), spv_check, tol*spv(0,0,0,1), test_OK)
28420 OK = OK .and. test_OK
2843 else
28440 test_OK = (abs((rho_ref+rho(0,0,0,1)) * (spv_ref + spv(0,0,0,1)) - 1.0) < tol)
28450 OK = OK .and. test_OK
28460 if (verbose) then
2847 write(mesg, '(ES16.8," and ",ES16.8,", ratio - 1 = ",ES16.8)') &
28480 rho_ref+rho(0,0,0,1), 1.0/(spv_ref + spv(0,0,0,1)), &
28490 (rho_ref+rho(0,0,0,1)) * (spv_ref + spv(0,0,0,1)) - 1.0
28500 if (test_OK) then
28510 write(stdout,*) "The values of "//trim(EOS_name)//" rho and 1/spv agree. "//trim(mesg)
2852 else
28530 write(stderr,*) "The values of "//trim(EOS_name)//" rho and 1/spv disagree. "//trim(mesg)
2854 endif
2855 endif
2856 endif
2857
2858 ! Check that the densities are consistent when the reference value is extracted
28590 call calculate_density(T(0,0,0), S(0,0,0), p(0,0,0), rho_nooff, EOS)
28600 test_OK = (abs(rho_nooff - (rho_ref + rho(0,0,0,1))) < tol*rho_nooff)
28610 OK = OK .and. test_OK
28620 if (verbose .and. .not.test_OK) then
2863 write(mesg, '(ES24.16," vs. ",ES24.16," with tolerance ",ES12.4)') &
28640 rho_ref+rho(0,0,0,1), rho_nooff, tol*rho_nooff
2865 write(stderr,*) "For "//trim(EOS_name)//&
28660 " rho with and without a reference value disagree: "//trim(mesg)
2867 endif
2868
2869 ! Check that the specific volumes are consistent when the reference value is extracted
28700 call calculate_spec_vol(T(0,0,0), S(0,0,0), p(0,0,0), spv_nooff, EOS)
28710 test_OK = (abs(spv_nooff - (spv_ref + spv(0,0,0,1))) < tol*rho_nooff)
28720 OK = OK .and. test_OK
28730 if (verbose .and. .not.test_OK) then
2874 write(mesg, '(ES24.16," vs. ",ES24.16," with tolerance ",ES12.4)') &
28750 spv_ref + spv(0,0,0,1), spv_nooff, tol*spv_nooff
2876 write(stderr,*) "For "//trim(EOS_name)//&
28770 " spv with and without a reference value disagree: "//trim(mesg)
2878 endif
2879
2880 ! Account for the factors of terms in the numerator and denominator when estimating roundoff
28810 if (order == 6) then
28820 count_fac = 110.0/60.0 ; count_fac2 = 1088.0/180.0
28830 elseif (order == 4) then ! Use values appropriate for 4th order schemes.
28840 count_fac = 18.0/12.0 ; count_fac2 = 64.0/12.0
2885 else ! Use values appropriate for 2nd order schemes.
28860 count_fac = 2.0/2.0 ; count_fac2 = 4.0
2887 endif
2888
2889 ! Check for the rate of convergence expected with a 4th or 6th order accurate discretization
2890 ! with a 20% margin of error and a tolerance for contributions from roundoff.
28910 tol_here = tol*abs(drho_dT) + count_fac*r_tol/dT
28920 OK = OK .and. check_FD(drho_dT, drho_dT_fd, tol_here, verbose, trim(EOS_name)//" drho_dT", order)
28930 tol_here = tol*abs(drho_dS) + count_fac*r_tol/dS
28940 OK = OK .and. check_FD(drho_dS, drho_dS_fd, tol_here, verbose, trim(EOS_name)//" drho_dS", order)
28950 tol_here = tol*abs(drho_dp) + count_fac*r_tol/dp
28960 OK = OK .and. check_FD(drho_dp, drho_dp_fd, tol_here, verbose, trim(EOS_name)//" drho_dp", order)
28970 tol_here = tol*abs(dSV_dT(1)) + count_fac*sv_tol/dT
28980 OK = OK .and. check_FD(dSV_dT(1), dSV_dT_fd, tol_here, verbose, trim(EOS_name)//" dSV_dT", order)
28990 tol_here = tol*abs(dSV_dS(1)) + count_fac*sv_tol/dS
29000 OK = OK .and. check_FD(dSV_dS(1), dSV_dS_fd, tol_here, verbose, trim(EOS_name)//" dSV_dS", order)
29010 if (test_2nd) then
29020 tol_here = tol*abs(drho_dT_dT) + count_fac2*r_tol/dT**2
29030 OK = OK .and. check_FD(drho_dT_dT, drho_dT_dT_fd, tol_here, verbose, trim(EOS_name)//" drho_dT_dT", order)
2904 ! The curvature in salinity is relatively weak, so looser tolerances are needed for some forms of EOS?
29050 tol_here = 10.0*(tol*abs(drho_dS_dS) + count_fac2*r_tol/dS**2)
29060 OK = OK .and. check_FD(drho_dS_dS, drho_dS_dS_fd, tol_here, verbose, trim(EOS_name)//" drho_dS_dS", order)
29070 tol_here = tol*abs(drho_dS_dT) + count_fac**2*r_tol/(dS*dT)
29080 OK = OK .and. check_FD(drho_dS_dT, drho_dS_dT_fd, tol_here, verbose, trim(EOS_name)//" drho_dS_dT", order)
29090 tol_here = tol*abs(drho_dT_dP) + count_fac**2*r_tol/(dT*dp)
29100 OK = OK .and. check_FD(drho_dT_dP, drho_dT_dP_fd, tol_here, verbose, trim(EOS_name)//" drho_dT_dP", order)
29110 tol_here = tol*abs(drho_dS_dP) + count_fac**2*r_tol/(dS*dp)
29120 OK = OK .and. check_FD(drho_dS_dP, drho_dS_dP_fd, tol_here, verbose, trim(EOS_name)//" drho_dS_dP", order)
2913 endif
2914
29150 if (test_avg_Sv) then
29160 tol_here = 0.5*tol*(abs(SpV_avg_a(1)) + abs(SpV_avg_q(1)))
29170 test_OK = (abs(SpV_avg_a(1) - SpV_avg_q(1)) < tol_here)
29180 if (verbose) then
2919 write(mesg, '(ES24.16," and ",ES24.16," differ by ",ES16.8," (",ES10.2,"), tol=",ES16.8)') &
29200 SpV_avg_a(1), SpV_avg_q(1), SpV_avg_a(1) - SpV_avg_q(1), &
29210 2.0*(SpV_avg_a(1) - SpV_avg_q(1)) / (abs(SpV_avg_a(1)) + abs(SpV_avg_q(1)) + tiny(SpV_avg_a(1))), &
29220 tol_here
29230 if (verbose .and. .not.test_OK) then
29240 write(stderr,*) "The values of "//trim(EOS_name)//" SpV_avg disagree. "//trim(mesg)
29250 elseif (verbose) then
29260 write(stdout,*) "The values of "//trim(EOS_name)//" SpV_avg agree: "//trim(mesg)
2927 endif
2928 endif
29290 OK = OK .and. test_OK
2930 endif
2931
29320 inconsistent = .not.OK
2933
2934 contains
2935
2936 !> Return a finite difference estimate of the first derivative of a field in arbitrary units [A B-1]
29370 real function first_deriv(R, dx, order)
2938 real, intent(in) :: R(-3:3) !< The field whose derivative is being taken, in arbitrary units [A]
2939 real, intent(in) :: dx !< The spacing in parameter space, in different arbitrary units [B]
2940 integer, intent(in) :: order !< The order of accuracy of the centered finite difference estimates (2, 4 or 6)
2941
29420 if (order == 6) then ! Find a 6th order accurate first derivative on a regular grid.
29430 first_deriv = (45.0*(R(1)-R(-1)) + (-9.0*(R(2)-R(-2)) + (R(3)-R(-3))) ) / (60.0 * dx)
29440 elseif (order == 4) then ! Find a 4th order accurate first derivative on a regular grid.
29450 first_deriv = (8.0*(R(1)-R(-1)) - (R(2)-R(-2)) ) / (12.0 * dx)
2946 else ! Find a 2nd order accurate first derivative on a regular grid.
29470 first_deriv = (R(1)-R(-1)) / (2.0 * dx)
2948 endif
29490 end function first_deriv
2950
2951 !> Return a finite difference estimate of the second derivative of a field in arbitrary units [A B-2]
29520 real function second_deriv(R, dx, order)
2953 real, intent(in) :: R(-3:3) !< The field whose derivative is being taken, in arbitrary units [A]
2954 real, intent(in) :: dx !< The spacing in parameter space, in different arbitrary units [B]
2955 integer, intent(in) :: order !< The order of accuracy of the centered finite difference estimates (2, 4 or 6)
2956
29570 if (order == 6) then ! Find a 6th order accurate second derivative on a regular grid.
29580 second_deriv = ( -490.0*R(0) + (270.0*(R(1)+R(-1)) + (-27.0*(R(2)+R(-2)) + 2.0*(R(3)+R(-3))) )) / (180.0 * dx**2)
29590 elseif (order == 4) then ! Find a 4th order accurate second derivative on a regular grid.
29600 second_deriv = ( -30.0*R(0) + (16.0*(R(1)+R(-1)) - (R(2)+R(-2))) ) / (12.0 * dx**2)
2961 else ! Find a 2nd order accurate second derivative on a regular grid.
29620 second_deriv = ( -2.0*R(0) + (R(1)+R(-1)) ) / dx**2
2963 endif
29640 end function second_deriv
2965
2966 !> Return a finite difference estimate of the second derivative with respect to two different
2967 !! parameters of a field in arbitrary units [A B-1 C-1]
29680 real function derivs_2d(R, dxdy, order)
2969 real, intent(in) :: R(-3:3,-3:3) !< The field whose derivative is being taken in arbitrary units [A]
2970 real, intent(in) :: dxdy !< The spacing in two directions in parameter space in different arbitrary units [B C]
2971 integer, intent(in) :: order !< The order of accuracy of the centered finite difference estimates (2, 4 or 6)
2972
2973 real :: dRdx(-3:3) ! The first derivative in one direction times the grid spacing in that direction [A]
2974 integer :: i
2975
29760 do i=-3,3
29770 dRdx(i) = first_deriv(R(:,i), 1.0, order)
2978 enddo
29790 derivs_2d = first_deriv(dRdx, dxdy, order)
2980
29810 end function derivs_2d
2982
2983 !> Check for the rate of convergence expected with a finite difference discretization
2984 !! with a 20% margin of error and a tolerance for contributions from roundoff.
29850 logical function check_FD(val, val_fd, tol, verbose, field_name, order)
2986 real, intent(in) :: val !< The derivative being checked, in arbitrary units [arbitrary]
2987 real, intent(in) :: val_fd(2) !< Two finite difference estimates of val taken with a spacing
2988 !! in parameter space and twice this spacing, in the same
2989 !! arbitrary units as val [arbitrary]
2990 real, intent(in) :: tol !< An estimated fractional tolerance due to roundoff [arbitrary]
2991 logical, intent(in) :: verbose !< If true, write results to stdout
2992 character(len=*), intent(in) :: field_name !< A name used to describe the field in error messages
2993 integer, intent(in) :: order !< The order of accuracy of the centered finite difference estimates (2, 4 or 6)
2994
2995 character(len=200) :: mesg
2996
29970 check_FD = ( abs(val_fd(1) - val) < (1.2*abs(val_fd(2) - val)/2**order + abs(tol)) )
2998
2999 write(mesg, '(ES24.16," and ",ES24.16," differ by ",ES16.8," (",ES10.2,"), tol=",ES16.8)') &
30000 val, val_fd(1), val - val_fd(1), &
30010 2.0*(val - val_fd(1)) / (abs(val) + abs(val_fd(1)) + tiny(val)), &
30020 (1.2*abs(val_fd(2) - val)/2**order + abs(tol))
3003 ! This message is useful for debugging the two estimates:
3004 ! write(mesg, '(ES16.8," and ",ES16.8," or ",ES16.8," differ by ",2ES16.8," (",2ES10.2"), tol=",ES16.8)') &
3005 ! val, val_fd(1), val_fd(2), val - val_fd(1), val - val_fd(2), &
3006 ! 2.0*(val - val_fd(1)) / (abs(val) + abs(val_fd(1)) + tiny(val)), &
3007 ! 2.0*(val - val_fd(2)) / (abs(val) + abs(val_fd(2)) + tiny(val)), &
3008 ! (1.2*abs(val_fd(2) - val)/2**order + abs(tol))
30090 if (verbose .and. .not.check_FD) then
30100 write(stderr,*) "The values of "//trim(field_name)//" disagree. "//trim(mesg)
30110 elseif (verbose) then
30120 write(stdout,*) "The values of "//trim(field_name)//" agree: "//trim(mesg)
3013 endif
30140 end function check_FD
3015
3016end function test_EOS_consistency
3017
30181end module MOM_EOS
3019
3020!> \namespace mom_eos
3021!!
3022!! The MOM_EOS module is a wrapper for various equations of state (i.e. Linear, Wright,
3023!! Wright_full, Wright_red, UNESCO, TEOS10, Roquet_SpV or Roquet_rho) and provides a uniform
3024!! interface to the rest of the model independent of which equation of state is being used.