MOM6
MOM_sum_output.F90
1 !> Reports integrated quantities for monitoring the model state
3 
4 ! This file is part of MOM6. See LICENSE.md for the license.
5 
6 use iso_fortran_env, only : int64
7 use mom_coms, only : sum_across_pes, pe_here, root_pe, num_pes, max_across_pes
8 use mom_coms, only : reproducing_sum, reproducing_sum_efp, efp_to_real, real_to_efp
9 use mom_coms, only : efp_type, operator(+), operator(-), assignment(=), efp_sum_across_pes
10 use mom_error_handler, only : mom_error, fatal, warning, is_root_pe, mom_mesg
12 use mom_forcing_type, only : forcing
13 use mom_grid, only : ocean_grid_type
15 use mom_io, only : create_file, fieldtype, flush_file, open_file, reopen_file
16 use mom_io, only : file_exists, slasher, vardesc, var_desc, write_field, get_filename_appendix
17 use mom_io, only : append_file, ascii_file, single_file, writeonly_file
19 use mom_open_boundary, only : obc_direction_e, obc_direction_w, obc_direction_n, obc_direction_s
20 use mom_time_manager, only : time_type, get_time, get_date, set_time, operator(>)
21 use mom_time_manager, only : operator(+), operator(-), operator(*), operator(/)
22 use mom_time_manager, only : operator(/=), operator(<=), operator(>=), operator(<)
23 use mom_time_manager, only : get_calendar_type, time_type_to_real, no_calendar
24 use mom_tracer_flow_control, only : tracer_flow_control_cs, call_tracer_stocks
28 use mpp_mod, only : mpp_chksum
29 
30 use netcdf
31 
32 implicit none ; private
33 
34 #include <MOM_memory.h>
35 
36 public write_energy, accumulate_net_input, mom_sum_output_init
37 
38 ! A note on unit descriptions in comments: MOM6 uses units that can be rescaled for dimensional
39 ! consistency testing. These are noted in comments with units like Z, H, L, and T, along with
40 ! their mks counterparts with notation like "a velocity [Z T-1 ~> m s-1]". If the units
41 ! vary with the Boussinesq approximation, the Boussinesq variant is given first.
42 
43 integer, parameter :: num_fields = 17 !< Number of diagnostic fields
44 character (*), parameter :: depth_chksum_attr = "bathyT_checksum"
45  !< Checksum attribute name of G%bathyT
46  !! over the compute domain
47 character (*), parameter :: area_chksum_attr = "mask2dT_areaT_checksum"
48  !< Checksum attribute of name of
49  !! G%mask2dT * G%areaT over the compute
50  !! domain
51 
52 !> A list of depths and corresponding globally integrated ocean area at each
53 !! depth and the ocean volume below each depth.
54 type :: depth_list
55  real :: depth !< A depth [Z ~> m].
56  real :: area !< The cross-sectional area of the ocean at that depth [L2 ~> m2].
57  real :: vol_below !< The ocean volume below that depth [Z m2 ~> m3].
58 end type depth_list
59 
60 !> The control structure for the MOM_sum_output module
61 type, public :: sum_output_cs ; private
62  type(depth_list), pointer, dimension(:) :: dl => null() !< The sorted depth list.
63  integer :: list_size !< length of sorting vector <= niglobal*njglobal
64 
65  integer, allocatable, dimension(:) :: lh
66  !< This saves the entry in DL with a volume just
67  !! less than the volume of fluid below the interface.
68  logical :: do_ape_calc !< If true, calculate the available potential energy of the
69  !! interfaces. Disabling this reduces the memory footprint of
70  !! high-PE-count models dramatically.
71  logical :: read_depth_list !< Read the depth list from a file if it exists
72  !! and write it if it doesn't.
73  character(len=200) :: depth_list_file !< The name of the depth list file.
74  real :: d_list_min_inc !< The minimum increment [Z ~> m], between the depths of the
75  !! entries in the depth-list file, 0 by default.
76  logical :: require_depth_list_chksum
77  !< Require matching checksums in Depth_list.nc when reading
78  !! the file.
79  logical :: update_depth_list_chksum
80  !< Automatically update the Depth_list.nc file if the
81  !! checksums are missing or do not match current values.
82  logical :: use_temperature !< If true, temperature and salinity are state variables.
83  type(efp_type) :: fresh_water_in_efp !< The total mass of fresh water added by surface fluxes on
84  !! this PE since the last time that write_energy was called [kg].
85  type(efp_type) :: net_salt_in_efp !< The total salt added by surface fluxes on this PE since
86  !! the last time that write_energy was called [ppt kg].
87  type(efp_type) :: net_heat_in_efp !< The total heat added by surface fluxes on this PE since
88  !! the last time that write_energy was called [J].
89  type(efp_type) :: heat_prev_efp !< The total amount of heat in the ocean the last
90  !! time that write_energy was called [J].
91  type(efp_type) :: salt_prev_efp !< The total amount of salt in the ocean the last
92  !! time that write_energy was called [ppt kg].
93  type(efp_type) :: mass_prev_efp !< The total ocean mass the last time that
94  !! write_energy was called [kg].
95  real :: dt_in_t !< The baroclinic dynamics time step [T ~> s].
96 
97  type(time_type) :: energysavedays !< The interval between writing the energies
98  !! and other integral quantities of the run.
99  type(time_type) :: energysavedays_geometric !< The starting interval for computing a geometric
100  !! progression of time deltas between calls to
101  !! write_energy. This interval will increase by a factor of 2.
102  !! after each call to write_energy.
103  logical :: energysave_geometric !< Logical to control whether calls to write_energy should
104  !! follow a geometric progression
105  type(time_type) :: write_energy_time !< The next time to write to the energy file.
106  type(time_type) :: geometric_end_time !< Time at which to stop the geometric progression
107  !! of calls to write_energy and revert to the standard
108  !! energysavedays interval
109 
110  real :: timeunit !< The length of the units for the time axis [s].
111  logical :: date_stamped_output !< If true, use dates (not times) in messages to stdout.
112  type(time_type) :: start_time !< The start time of the simulation.
113  ! Start_time is set in MOM_initialization.F90
114  integer, pointer :: ntrunc => null() !< The number of times the velocity has been
115  !! truncated since the last call to write_energy.
116  real :: max_energy !< The maximum permitted energy per unit mass. If there is
117  !! more energy than this, the model should stop [m2 s-2].
118  integer :: maxtrunc !< The number of truncations per energy save
119  !! interval at which the run is stopped.
120  logical :: write_stocks !< If true, write the integrated tracer amounts
121  !! to stdout when the energy files are written.
122  integer :: previous_calls = 0 !< The number of times write_energy has been called.
123  integer :: prev_n = 0 !< The value of n from the last call.
124  integer :: fileenergy_nc !< NetCDF id of the energy file.
125  integer :: fileenergy_ascii !< The unit number of the ascii version of the energy file.
126  type(fieldtype), dimension(NUM_FIELDS+MAX_FIELDS_) :: &
127  fields !< fieldtype variables for the output fields.
128  character(len=200) :: energyfile !< The name of the energy file with path.
129 end type sum_output_cs
130 
131 contains
132 
133 !> MOM_sum_output_init initializes the parameters and settings for the MOM_sum_output module.
134 subroutine mom_sum_output_init(G, US, param_file, directory, ntrnc, &
135  Input_start_time, CS)
136  type(ocean_grid_type), intent(in) :: g !< The ocean's grid structure.
137  type(unit_scale_type), intent(in) :: us !< A dimensional unit scaling type
138  type(param_file_type), intent(in) :: param_file !< A structure to parse for run-time
139  !! parameters.
140  character(len=*), intent(in) :: directory !< The directory where the energy file goes.
141  integer, target, intent(inout) :: ntrnc !< The integer that stores the number of times
142  !! the velocity has been truncated since the
143  !! last call to write_energy.
144  type(time_type), intent(in) :: input_start_time !< The start time of the simulation.
145  type(sum_output_cs), pointer :: cs !< A pointer that is set to point to the
146  !! control structure for this module.
147  ! Local variables
148  real :: time_unit ! The time unit in seconds for ENERGYSAVEDAYS.
149  real :: rho_0 ! A reference density [kg m-3]
150  real :: maxvel ! The maximum permitted velocity [m s-1]
151 ! This include declares and sets the variable "version".
152 #include "version_variable.h"
153  character(len=40) :: mdl = "MOM_sum_output" ! This module's name.
154  character(len=200) :: energyfile ! The name of the energy file.
155  character(len=32) :: filename_appendix = '' !fms appendix to filename for ensemble runs
156 
157  if (associated(cs)) then
158  call mom_error(warning, "MOM_sum_output_init called with associated control structure.")
159  return
160  endif
161  allocate(cs)
162 
163  ! Read all relevant parameters and write them to the model log.
164  call log_version(param_file, mdl, version, "")
165  call get_param(param_file, mdl, "CALCULATE_APE", cs%do_APE_calc, &
166  "If true, calculate the available potential energy of "//&
167  "the interfaces. Setting this to false reduces the "//&
168  "memory footprint of high-PE-count models dramatically.", &
169  default=.true.)
170  call get_param(param_file, mdl, "WRITE_STOCKS", cs%write_stocks, &
171  "If true, write the integrated tracer amounts to stdout "//&
172  "when the energy files are written.", default=.true.)
173  call get_param(param_file, mdl, "ENABLE_THERMODYNAMICS", cs%use_temperature, &
174  "If true, Temperature and salinity are used as state "//&
175  "variables.", default=.true.)
176  call get_param(param_file, mdl, "DT", cs%dt_in_T, &
177  "The (baroclinic) dynamics time step.", &
178  units="s", scale=us%s_to_T, fail_if_missing=.true.)
179  call get_param(param_file, mdl, "MAXTRUNC", cs%maxtrunc, &
180  "The run will be stopped, and the day set to a very "//&
181  "large value if the velocity is truncated more than "//&
182  "MAXTRUNC times between energy saves. Set MAXTRUNC to 0 "//&
183  "to stop if there is any truncation of velocities.", &
184  units="truncations save_interval-1", default=0)
185 
186  call get_param(param_file, mdl, "MAX_ENERGY", cs%max_Energy, &
187  "The maximum permitted average energy per unit mass; the "//&
188  "model will be stopped if there is more energy than "//&
189  "this. If zero or negative, this is set to 10*MAXVEL^2.", &
190  units="m2 s-2", default=0.0)
191  if (cs%max_Energy <= 0.0) then
192  call get_param(param_file, mdl, "MAXVEL", maxvel, &
193  "The maximum velocity allowed before the velocity "//&
194  "components are truncated.", units="m s-1", default=3.0e8)
195  cs%max_Energy = 10.0 * maxvel**2
196  call log_param(param_file, mdl, "MAX_ENERGY as used", cs%max_Energy)
197  endif
198 
199  call get_param(param_file, mdl, "ENERGYFILE", energyfile, &
200  "The file to use to write the energies and globally "//&
201  "summed diagnostics.", default="ocean.stats")
202 
203  !query fms_io if there is a filename_appendix (for ensemble runs)
204  call get_filename_appendix(filename_appendix)
205  if (len_trim(filename_appendix) > 0) then
206  energyfile = trim(energyfile) //'.'//trim(filename_appendix)
207  endif
208 
209  cs%energyfile = trim(slasher(directory))//trim(energyfile)
210  call log_param(param_file, mdl, "output_path/ENERGYFILE", cs%energyfile)
211 #ifdef STATSLABEL
212  cs%energyfile = trim(cs%energyfile)//"."//trim(adjustl(statslabel))
213 #endif
214 
215  call get_param(param_file, mdl, "DATE_STAMPED_STDOUT", cs%date_stamped_output, &
216  "If true, use dates (not times) in messages to stdout", &
217  default=.true.)
218  call get_param(param_file, mdl, "TIMEUNIT", cs%Timeunit, &
219  "The time unit in seconds a number of input fields", &
220  units="s", default=86400.0)
221  if (cs%Timeunit < 0.0) cs%Timeunit = 86400.0
222 
223 
224 
225  if (cs%do_APE_calc) then
226  call get_param(param_file, mdl, "READ_DEPTH_LIST", cs%read_depth_list, &
227  "Read the depth list from a file if it exists or "//&
228  "create that file otherwise.", default=.false.)
229  call get_param(param_file, mdl, "DEPTH_LIST_MIN_INC", cs%D_list_min_inc, &
230  "The minimum increment between the depths of the "//&
231  "entries in the depth-list file.", &
232  units="m", default=1.0e-10, scale=us%m_to_Z)
233  if (cs%read_depth_list) then
234  call get_param(param_file, mdl, "DEPTH_LIST_FILE", cs%depth_list_file, &
235  "The name of the depth list file.", default="Depth_list.nc")
236  if (scan(cs%depth_list_file,'/') == 0) &
237  cs%depth_list_file = trim(slasher(directory))//trim(cs%depth_list_file)
238 
239  call get_param(param_file, mdl, "REQUIRE_DEPTH_LIST_CHECKSUMS", &
240  cs%require_depth_list_chksum, &
241  "Require that matching checksums be in Depth_list.nc "//&
242  "when reading the file.", default=.true.)
243  if (.not. cs%require_depth_list_chksum) &
244  call get_param(param_file, mdl, "UPDATE_DEPTH_LIST_CHECKSUMS", &
245  cs%update_depth_list_chksum, &
246  "Automatically update the Depth_list.nc file if the "//&
247  "checksums are missing or do not match current values.", &
248  default=.false.)
249  endif
250 
251  allocate(cs%lH(g%ke))
252  call depth_list_setup(g, us, cs)
253  else
254  cs%list_size = 0
255  endif
256 
257  call get_param(param_file, mdl, "TIMEUNIT", time_unit, &
258  "The time unit for ENERGYSAVEDAYS.", &
259  units="s", default=86400.0)
260  call get_param(param_file, mdl, "ENERGYSAVEDAYS",cs%energysavedays, &
261  "The interval in units of TIMEUNIT between saves of the "//&
262  "energies of the run and other globally summed diagnostics.",&
263  default=set_time(0,days=1), timeunit=time_unit)
264  call get_param(param_file, mdl, "ENERGYSAVEDAYS_GEOMETRIC",cs%energysavedays_geometric, &
265  "The starting interval in units of TIMEUNIT for the first call "//&
266  "to save the energies of the run and other globally summed diagnostics. "//&
267  "The interval increases by a factor of 2. after each call to write_energy.",&
268  default=set_time(seconds=0), timeunit=time_unit)
269 
270  if ((time_type_to_real(cs%energysavedays_geometric) > 0.) .and. &
271  (cs%energysavedays_geometric < cs%energysavedays)) then
272  cs%energysave_geometric = .true.
273  else
274  cs%energysave_geometric = .false.
275  endif
276 
277  cs%Start_time = input_start_time
278  cs%ntrunc => ntrnc
279 
280 end subroutine mom_sum_output_init
281 
282 !> MOM_sum_output_end deallocates memory used by the MOM_sum_output module.
283 subroutine mom_sum_output_end(CS)
284  type(Sum_output_CS), pointer :: CS !< The control structure returned by a
285  !! previous call to MOM_sum_output_init.
286  if (associated(cs)) then
287  if (cs%do_APE_calc) then
288  deallocate(cs%lH, cs%DL)
289  endif
290 
291  deallocate(cs)
292  endif
293 end subroutine mom_sum_output_end
294 
295 !> This subroutine calculates and writes the total model energy, the energy and
296 !! mass of each layer, and other globally integrated physical quantities.
297 subroutine write_energy(u, v, h, tv, day, n, G, GV, US, CS, tracer_CSp, OBC, dt_forcing)
298  type(ocean_grid_type), intent(in) :: g !< The ocean's grid structure.
299  type(verticalgrid_type), intent(in) :: gv !< The ocean's vertical grid structure.
300  type(unit_scale_type), intent(in) :: us !< A dimensional unit scaling type
301  real, dimension(SZIB_(G),SZJ_(G),SZK_(G)), &
302  intent(in) :: u !< The zonal velocity [L T-1 ~> m s-1].
303  real, dimension(SZI_(G),SZJB_(G),SZK_(G)), &
304  intent(in) :: v !< The meridional velocity [L T-1 ~> m s-1].
305  real, dimension(SZI_(G),SZJ_(G),SZK_(G)), &
306  intent(in) :: h !< Layer thicknesses [H ~> m or kg m-2].
307  type(thermo_var_ptrs), intent(in) :: tv !< A structure pointing to various
308  !! thermodynamic variables.
309  type(time_type), intent(in) :: day !< The current model time.
310  integer, intent(in) :: n !< The time step number of the
311  !! current execution.
312  type(sum_output_cs), pointer :: cs !< The control structure returned by a
313  !! previous call to MOM_sum_output_init.
314  type(tracer_flow_control_cs), &
315  optional, pointer :: tracer_csp !< tracer control structure.
316  type(ocean_obc_type), &
317  optional, pointer :: obc !< Open boundaries control structure.
318  type(time_type), optional, intent(in) :: dt_forcing !< The forcing time step
319  ! Local variables
320  real :: eta(szi_(g),szj_(g),szk_(g)+1) ! The height of interfaces [Z ~> m].
321  real :: areatm(szi_(g),szj_(g)) ! A masked version of areaT [L2 ~> m2].
322  real :: ke(szk_(g)) ! The total kinetic energy of a layer [J].
323  real :: pe(szk_(g)+1)! The available potential energy of an interface [J].
324  real :: ke_tot ! The total kinetic energy [J].
325  real :: pe_tot ! The total available potential energy [J].
326  real :: z_0ape(szk_(g)+1) ! The uniform depth which overlies the same
327  ! volume as is below an interface [Z ~> m].
328  real :: h_0ape(szk_(g)+1) ! A version of Z_0APE, converted to m, usually positive.
329  real :: toten ! The total kinetic & potential energies of
330  ! all layers [J] (i.e. kg m2 s-2).
331  real :: en_mass ! The total kinetic and potential energies divided by
332  ! the total mass of the ocean [m2 s-2].
333  real :: vol_lay(szk_(g)) ! The volume of fluid in a layer [Z L2 ~> m3].
334  real :: volbelow ! The volume of all layers beneath an interface [Z L2 ~> m3].
335  real :: mass_lay(szk_(g)) ! The mass of fluid in a layer [kg].
336  real :: mass_tot ! The total mass of the ocean [kg].
337  real :: vol_tot ! The total ocean volume [m3].
338  real :: mass_chg ! The change in total ocean mass of fresh water since
339  ! the last call to this subroutine [kg].
340  real :: mass_anom ! The change in fresh water that cannot be accounted for
341  ! by the surface fluxes [kg].
342  real :: salt ! The total amount of salt in the ocean [ppt kg].
343  real :: salt_chg ! The change in total ocean salt since the last call
344  ! to this subroutine [ppt kg].
345  real :: salt_anom ! The change in salt that cannot be accounted for by
346  ! the surface fluxes [ppt kg].
347  real :: salin ! The mean salinity of the ocean [ppt].
348  real :: salin_chg ! The change in total salt since the last call
349  ! to this subroutine divided by total mass [ppt].
350  real :: salin_anom ! The change in total salt that cannot be accounted for by
351  ! the surface fluxes divided by total mass [ppt].
352  real :: heat ! The total amount of Heat in the ocean [J].
353  real :: heat_chg ! The change in total ocean heat since the last call to this subroutine [J].
354  real :: heat_anom ! The change in heat that cannot be accounted for by the surface fluxes [J].
355  real :: temp ! The mean potential temperature of the ocean [degC].
356  real :: temp_chg ! The change in total heat divided by total heat capacity
357  ! of the ocean since the last call to this subroutine, degC.
358  real :: temp_anom ! The change in total heat that cannot be accounted for
359  ! by the surface fluxes, divided by the total heat
360  ! capacity of the ocean [degC].
361  real :: hint ! The deviation of an interface from H [Z ~> m].
362  real :: hbot ! 0 if the basin is deeper than H, or the
363  ! height of the basin depth over H otherwise [Z ~> m].
364  ! This makes PE only include real fluid.
365  real :: hbelow ! The depth of fluid in all layers beneath an interface [Z ~> m].
366  type(efp_type) :: &
367  mass_efp, & ! The total mass of the ocean in extended fixed point form [kg].
368  salt_efp, & ! The total amount of salt in the ocean in extended fixed point form [ppt kg].
369  heat_efp, & ! The total amount of heat in the ocean in extended fixed point form [J].
370  salt_chg_efp, & ! The change in total ocean salt since the last call to this subroutine [ppt kg].
371  heat_chg_efp, & ! The change in total ocean heat since the last call to this subroutine [J].
372  mass_chg_efp, & ! The change in total ocean mass of fresh water since
373  ! the last call to this subroutine [kg].
374  salt_anom_efp, & ! The change in salt that cannot be accounted for by the surface
375  ! fluxes [ppt kg].
376  heat_anom_efp, & ! The change in heat that cannot be accounted for by the surface fluxes [J].
377  mass_anom_efp ! The change in fresh water that cannot be accounted for by the surface
378  ! fluxes [kg].
379  type(efp_type), dimension(5) :: efp_list ! An array of EFP types for joint global sums.
380  real :: cfl_iarea ! Direction-based inverse area used in CFL test [L-2].
381  real :: cfl_trans ! A transport-based definition of the CFL number [nondim].
382  real :: cfl_lin ! A simpler definition of the CFL number [nondim].
383  real :: max_cfl(2) ! The maxima of the CFL numbers [nondim].
384  real :: irho0 ! The inverse of the reference density [m3 kg-1].
385  real, dimension(SZI_(G),SZJ_(G),SZK_(G)) :: &
386  tmp1 ! A temporary array
387  real, dimension(SZI_(G),SZJ_(G),SZK_(G)+1) :: &
388  pe_pt ! The potential energy at each point [J].
389  real, dimension(SZI_(G),SZJ_(G)) :: &
390  temp_int, salt_int ! Layer and cell integrated heat and salt [J] and [g Salt].
391  real :: hl2_to_kg ! A conversion factor from a thickness-volume to mass [kg H-1 L-2 ~> kg m-3 or 1]
392  real :: ke_scale_factor ! The combination of unit rescaling factors in the kinetic energy
393  ! calculation [kg T2 H-1 L-2 s-2 ~> kg m-3 or nondim]
394  real :: pe_scale_factor ! The combination of unit rescaling factors in the potential energy
395  ! calculation [kg T2 R-1 Z-1 L-2 s-2 ~> nondim]
396  integer :: num_nc_fields ! The number of fields that will actually go into
397  ! the NetCDF file.
398  integer :: i, j, k, is, ie, js, je, ns, nz, m, isq, ieq, jsq, jeq, isr, ier, jsr, jer
399  integer :: l, lbelow, labove ! indices of deep_area_vol, used to find Z_0APE.
400  ! lbelow & labove are lower & upper limits for l
401  ! in the search for the entry in lH to use.
402  integer :: start_of_day, num_days
403  real :: reday, var
404  character(len=240) :: energypath_nc
405  character(len=200) :: mesg
406  character(len=32) :: mesg_intro, time_units, day_str, n_str, date_str
407  logical :: date_stamped
408  type(time_type) :: dt_force ! A time_type version of the forcing timestep.
409  real :: tr_stocks(max_fields_)
410  real :: tr_min(max_fields_), tr_max(max_fields_)
411  real :: tr_min_x(max_fields_), tr_min_y(max_fields_), tr_min_z(max_fields_)
412  real :: tr_max_x(max_fields_), tr_max_y(max_fields_), tr_max_z(max_fields_)
413  logical :: tr_minmax_got(max_fields_) = .false.
414  character(len=40), dimension(MAX_FIELDS_) :: &
415  tr_names, tr_units
416  integer :: ntr_stocks
417  integer :: iyear, imonth, iday, ihour, iminute, isecond, itick ! For call to get_date()
418  logical :: local_open_bc
419  type(obc_segment_type), pointer :: segment => null()
420 
421  ! A description for output of each of the fields.
422  type(vardesc) :: vars(num_fields+max_fields_)
423 
424  ! write_energy_time is the next integral multiple of energysavedays.
425  dt_force = set_time(seconds=2) ; if (present(dt_forcing)) dt_force = dt_forcing
426  if (cs%previous_calls == 0) then
427  if (cs%energysave_geometric) then
428  if (cs%energysavedays_geometric < cs%energysavedays) then
429  cs%write_energy_time = day + cs%energysavedays_geometric
430  cs%geometric_end_time = cs%Start_time + cs%energysavedays * &
431  (1 + (day - cs%Start_time) / cs%energysavedays)
432  else
433  cs%write_energy_time = cs%Start_time + cs%energysavedays * &
434  (1 + (day - cs%Start_time) / cs%energysavedays)
435  endif
436  else
437  cs%write_energy_time = cs%Start_time + cs%energysavedays * &
438  (1 + (day - cs%Start_time) / cs%energysavedays)
439  endif
440  elseif (day + (dt_force/2) <= cs%write_energy_time) then
441  return ! Do not write this step
442  else ! Determine the next write time before proceeding
443  if (cs%energysave_geometric) then
444  if (cs%write_energy_time + cs%energysavedays_geometric >= &
445  cs%geometric_end_time) then
446  cs%write_energy_time = cs%geometric_end_time
447  cs%energysave_geometric = .false. ! stop geometric progression
448  else
449  cs%write_energy_time = cs%write_energy_time + cs%energysavedays_geometric
450  endif
451  cs%energysavedays_geometric = cs%energysavedays_geometric*2
452  else
453  cs%write_energy_time = cs%write_energy_time + cs%energysavedays
454  endif
455  endif
456 
457  num_nc_fields = 17
458  if (.not.cs%use_temperature) num_nc_fields = 11
459  vars(1) = var_desc("Ntrunc","Nondim","Number of Velocity Truncations",'1','1')
460  vars(2) = var_desc("En","Joules","Total Energy",'1','1')
461  vars(3) = var_desc("APE","Joules","Total Interface APE",'1','i')
462  vars(4) = var_desc("KE","Joules","Total Layer KE",'1','L')
463  vars(5) = var_desc("H0","meter","Zero APE Depth of Interface",'1','i')
464  vars(6) = var_desc("Mass_lay","kg","Total Layer Mass",'1','L')
465  vars(7) = var_desc("Mass","kg","Total Mass",'1','1')
466  vars(8) = var_desc("Mass_chg","kg","Total Mass Change between Entries",'1','1')
467  vars(9) = var_desc("Mass_anom","kg","Anomalous Total Mass Change",'1','1')
468  vars(10) = var_desc("max_CFL_trans","Nondim","Maximum finite-volume CFL",'1','1')
469  vars(11) = var_desc("max_CFL_lin","Nondim","Maximum finite-difference CFL",'1','1')
470  if (cs%use_temperature) then
471  vars(12) = var_desc("Salt","kg","Total Salt",'1','1')
472  vars(13) = var_desc("Salt_chg","kg","Total Salt Change between Entries",'1','1')
473  vars(14) = var_desc("Salt_anom","kg","Anomalous Total Salt Change",'1','1')
474  vars(15) = var_desc("Heat","Joules","Total Heat",'1','1')
475  vars(16) = var_desc("Heat_chg","Joules","Total Heat Change between Entries",'1','1')
476  vars(17) = var_desc("Heat_anom","Joules","Anomalous Total Heat Change",'1','1')
477  endif
478 
479  local_open_bc = .false.
480  if (present(obc)) then ; if (associated(obc)) then
481  local_open_bc = (obc%open_u_BCs_exist_globally .or. obc%open_v_BCs_exist_globally)
482  endif ; endif
483 
484  is = g%isc ; ie = g%iec ; js = g%jsc ; je = g%jec ; nz = g%ke
485  isq = g%IscB ; ieq = g%IecB ; jsq = g%JscB ; jeq = g%JecB
486  isr = is - (g%isd-1) ; ier = ie - (g%isd-1) ; jsr = js - (g%jsd-1) ; jer = je - (g%jsd-1)
487 
488 
489  hl2_to_kg = gv%H_to_kg_m2*us%L_to_m**2
490 
491  if (.not.associated(cs)) call mom_error(fatal, &
492  "write_energy: Module must be initialized before it is used.")
493 
494  do j=js,je ; do i=is,ie
495  areatm(i,j) = g%mask2dT(i,j)*g%areaT(i,j)
496  enddo ; enddo
497 
498  if (gv%Boussinesq) then
499  tmp1(:,:,:) = 0.0
500  do k=1,nz ; do j=js,je ; do i=is,ie
501  tmp1(i,j,k) = h(i,j,k) * (hl2_to_kg*areatm(i,j))
502  enddo ; enddo ; enddo
503 
504  ! This block avoids using the points beyond an open boundary condition
505  ! in the accumulation of mass, but perhaps it would be unnecessary if there
506  ! were a more judicious use of masks in the loops 4 or 7 lines above.
507  if (local_open_bc) then
508  do ns=1, obc%number_of_segments
509  segment => obc%segment(ns)
510  if (.not. segment%on_pe .or. segment%specified) cycle
511  i=segment%HI%IsdB ; j=segment%HI%JsdB
512  if (segment%direction == obc_direction_e) then
513  do k=1,nz ; do j=segment%HI%jsd,segment%HI%jed
514  tmp1(i+1,j,k) = 0.0
515  enddo ; enddo
516  elseif (segment%direction == obc_direction_w) then
517  do k=1,nz ; do j=segment%HI%jsd,segment%HI%jed
518  tmp1(i,j,k) = 0.0
519  enddo ; enddo
520  elseif (segment%direction == obc_direction_n) then
521  do k=1,nz ; do i=segment%HI%isd,segment%HI%ied
522  tmp1(i,j+1,k) = 0.0
523  enddo ; enddo
524  elseif (segment%direction == obc_direction_s) then
525  do k=1,nz ; do i=segment%HI%isd,segment%HI%ied
526  tmp1(i,j,k) = 0.0
527  enddo ; enddo
528  endif
529  enddo
530  endif
531 
532  mass_tot = reproducing_sum(tmp1, isr, ier, jsr, jer, sums=mass_lay, efp_sum=mass_efp)
533  do k=1,nz ; vol_lay(k) = (us%m_to_L**2*gv%H_to_Z/gv%H_to_kg_m2)*mass_lay(k) ; enddo
534  else
535  tmp1(:,:,:) = 0.0
536  if (cs%do_APE_calc) then
537  do k=1,nz ; do j=js,je ; do i=is,ie
538  tmp1(i,j,k) = hl2_to_kg * h(i,j,k) * areatm(i,j)
539  enddo ; enddo ; enddo
540  mass_tot = reproducing_sum(tmp1, isr, ier, jsr, jer, sums=mass_lay, efp_sum=mass_efp)
541 
542  call find_eta(h, tv, g, gv, us, eta)
543  do k=1,nz ; do j=js,je ; do i=is,ie
544  tmp1(i,j,k) = us%Z_to_m*us%L_to_m**2*(eta(i,j,k)-eta(i,j,k+1)) * areatm(i,j)
545  enddo ; enddo ; enddo
546  vol_tot = reproducing_sum(tmp1, isr, ier, jsr, jer, sums=vol_lay)
547  do k=1,nz ; vol_lay(k) = us%m_to_Z*us%m_to_L**2 * vol_lay(k) ; enddo
548  else
549  do k=1,nz ; do j=js,je ; do i=is,ie
550  tmp1(i,j,k) = hl2_to_kg * h(i,j,k) * areatm(i,j)
551  enddo ; enddo ; enddo
552  mass_tot = reproducing_sum(tmp1, isr, ier, jsr, jer, sums=mass_lay, efp_sum=mass_efp)
553  do k=1,nz ; vol_lay(k) = us%m_to_Z*us%m_to_L**2*us%kg_m3_to_R * (mass_lay(k) / gv%Rho0) ; enddo
554  endif
555  endif ! Boussinesq
556 
557  ntr_stocks = 0
558  if (present(tracer_csp)) then
559  call call_tracer_stocks(h, tr_stocks, g, gv, tracer_csp, stock_names=tr_names, &
560  stock_units=tr_units, num_stocks=ntr_stocks,&
561  got_min_max=tr_minmax_got, global_min=tr_min, global_max=tr_max, &
562  xgmin=tr_min_x, ygmin=tr_min_y, zgmin=tr_min_z,&
563  xgmax=tr_max_x, ygmax=tr_max_y, zgmax=tr_max_z)
564  if (ntr_stocks > 0) then
565  do m=1,ntr_stocks
566  vars(num_nc_fields+m) = var_desc(tr_names(m), units=tr_units(m), &
567  longname=tr_names(m), hor_grid='1', z_grid='1')
568  enddo
569  num_nc_fields = num_nc_fields + ntr_stocks
570  endif
571  endif
572 
573  if (cs%previous_calls == 0) then
574 
575  cs%mass_prev_EFP = mass_efp
576  cs%fresh_water_in_EFP = real_to_efp(0.0)
577  if (cs%use_temperature) then
578  cs%net_salt_in_EFP = real_to_efp(0.0) ; cs%net_heat_in_EFP = real_to_efp(0.0)
579  endif
580 
581  ! Reopen or create a text output file, with an explanatory header line.
582  if (is_root_pe()) then
583  if (day > cs%Start_time) then
584  call open_file(cs%fileenergy_ascii, trim(cs%energyfile), &
585  action=append_file, form=ascii_file, nohdrs=.true.)
586  else
587  call open_file(cs%fileenergy_ascii, trim(cs%energyfile), &
588  action=writeonly_file, form=ascii_file, nohdrs=.true.)
589  if (abs(cs%timeunit - 86400.0) < 1.0) then
590  if (cs%use_temperature) then
591  write(cs%fileenergy_ascii,'(" Step,",7x,"Day, Truncs, &
592  &Energy/Mass, Maximum CFL, Mean Sea Level, Total Mass, Mean Salin, &
593  &Mean Temp, Frac Mass Err, Salin Err, Temp Err")')
594  write(cs%fileenergy_ascii,'(12x,"[days]",17x,"[m2 s-2]",11x,"[Nondim]",7x,"[m]",13x,&
595  &"[kg]",9x,"[PSU]",6x,"[degC]",7x,"[Nondim]",8x,"[PSU]",8x,"[degC]")')
596  else
597  write(cs%fileenergy_ascii,'(" Step,",7x,"Day, Truncs, &
598  &Energy/Mass, Maximum CFL, Mean sea level, Total Mass, Frac Mass Err")')
599  write(cs%fileenergy_ascii,'(12x,"[days]",17x,"[m2 s-2]",11x,"[Nondim]",8x,"[m]",13x,&
600  &"[kg]",11x,"[Nondim]")')
601  endif
602  else
603  if ((cs%timeunit >= 0.99) .and. (cs%timeunit < 1.01)) then
604  time_units = " [seconds] "
605  elseif ((cs%timeunit >= 3599.0) .and. (cs%timeunit < 3601.0)) then
606  time_units = " [hours] "
607  elseif ((cs%timeunit >= 86399.0) .and. (cs%timeunit < 86401.0)) then
608  time_units = " [days] "
609  elseif ((cs%timeunit >= 3.0e7) .and. (cs%timeunit < 3.2e7)) then
610  time_units = " [years] "
611  else
612  write(time_units,'(9x,"[",es8.2," s] ")') cs%timeunit
613  endif
614 
615  if (cs%use_temperature) then
616  write(cs%fileenergy_ascii,'(" Step,",7x,"Time, Truncs, &
617  &Energy/Mass, Maximum CFL, Mean Sea Level, Total Mass, Mean Salin, &
618  &Mean Temp, Frac Mass Err, Salin Err, Temp Err")')
619  write(cs%fileenergy_ascii,'(A25,10x,"[m2 s-2]",11x,"[Nondim]",7x,"[m]",13x,&
620  &"[kg]",9x,"[PSU]",6x,"[degC]",7x,"[Nondim]",8x,"[PSU]",6x,&
621  &"[degC]")') time_units
622  else
623  write(cs%fileenergy_ascii,'(" Step,",7x,"Time, Truncs, &
624  &Energy/Mass, Maximum CFL, Mean sea level, Total Mass, Frac Mass Err")')
625  write(cs%fileenergy_ascii,'(A25,10x,"[m2 s-2]",11x,"[Nondim]",8x,"[m]",13x,&
626  &"[kg]",11x,"[Nondim]")') time_units
627  endif
628  endif
629  endif
630  endif
631 
632  energypath_nc = trim(cs%energyfile) // ".nc"
633  if (day > cs%Start_time) then
634  call reopen_file(cs%fileenergy_nc, trim(energypath_nc), vars, &
635  num_nc_fields, cs%fields, single_file, cs%timeunit, &
636  g=g, gv=gv)
637  else
638  call create_file(cs%fileenergy_nc, trim(energypath_nc), vars, &
639  num_nc_fields, cs%fields, single_file, cs%timeunit, &
640  g=g, gv=gv)
641  endif
642  endif
643 
644  if (cs%do_APE_calc) then
645  lbelow = 1 ; volbelow = 0.0
646  do k=nz,1,-1
647  volbelow = volbelow + vol_lay(k)
648  if ((volbelow >= cs%DL(cs%lH(k))%vol_below) .and. &
649  (volbelow < cs%DL(cs%lH(k)+1)%vol_below)) then
650  l = cs%lH(k)
651  else
652  labove=cs%list_size+1
653  l = (labove + lbelow) / 2
654  do while (l > lbelow)
655  if (volbelow < cs%DL(l)%vol_below) then ; labove = l
656  else ; lbelow = l ; endif
657  l = (labove + lbelow) / 2
658  enddo
659  cs%lH(k) = l
660  endif
661  lbelow = l
662  z_0ape(k) = cs%DL(l)%depth - (volbelow - cs%DL(l)%vol_below) / cs%DL(l)%area
663  enddo
664  z_0ape(nz+1) = cs%DL(2)%depth
665 
666  ! Calculate the Available Potential Energy integrated over each interface. With a nonlinear
667  ! equation of state or with a bulk mixed layer this calculation is only approximate.
668  ! With an ALE model this does not make sense and should be revisited.
669  pe_scale_factor = us%RZ_to_kg_m2*us%L_to_m**2*us%L_T_to_m_s**2
670  pe_pt(:,:,:) = 0.0
671  if (gv%Boussinesq) then
672  do j=js,je ; do i=is,ie
673  hbelow = 0.0
674  do k=nz,1,-1
675  hbelow = hbelow + h(i,j,k) * gv%H_to_Z
676  hint = z_0ape(k) + (hbelow - g%bathyT(i,j))
677  hbot = z_0ape(k) - g%bathyT(i,j)
678  hbot = (hbot + abs(hbot)) * 0.5
679  pe_pt(i,j,k) = (0.5 * pe_scale_factor * areatm(i,j)) * (gv%Rho0*gv%g_prime(k)) * &
680  (hint * hint - hbot * hbot)
681  enddo
682  enddo ; enddo
683  else
684  do j=js,je ; do i=is,ie
685  do k=nz,1,-1
686  hint = z_0ape(k) + eta(i,j,k) ! eta and H_0 have opposite signs.
687  hbot = max(z_0ape(k) - g%bathyT(i,j), 0.0)
688  pe_pt(i,j,k) = (0.5 * pe_scale_factor * areatm(i,j) * (gv%Rho0*gv%g_prime(k))) * &
689  (hint * hint - hbot * hbot)
690  enddo
691  enddo ; enddo
692  endif
693 
694  pe_tot = reproducing_sum(pe_pt, isr, ier, jsr, jer, sums=pe)
695  do k=1,nz+1 ; h_0ape(k) = us%Z_to_m*z_0ape(k) ; enddo
696  else
697  pe_tot = 0.0
698  do k=1,nz+1 ; pe(k) = 0.0 ; h_0ape(k) = 0.0 ; enddo
699  endif
700 
701 ! Calculate the Kinetic Energy integrated over each layer.
702  ke_scale_factor = hl2_to_kg*us%L_T_to_m_s**2
703  tmp1(:,:,:) = 0.0
704  do k=1,nz ; do j=js,je ; do i=is,ie
705  tmp1(i,j,k) = (0.25 * ke_scale_factor * (areatm(i,j) * h(i,j,k))) * &
706  ((u(i-1,j,k)**2 + u(i,j,k)**2) + (v(i,j-1,k)**2 + v(i,j,k)**2))
707  enddo ; enddo ; enddo
708 
709  ke_tot = reproducing_sum(tmp1, isr, ier, jsr, jer, sums=ke)
710 
711  toten = ke_tot + pe_tot
712 
713  salt = 0.0 ; heat = 0.0
714  if (cs%use_temperature) then
715  temp_int(:,:) = 0.0 ; salt_int(:,:) = 0.0
716  do k=1,nz ; do j=js,je ; do i=is,ie
717  salt_int(i,j) = salt_int(i,j) + tv%S(i,j,k) * &
718  (h(i,j,k)*(hl2_to_kg * areatm(i,j)))
719  temp_int(i,j) = temp_int(i,j) + (us%Q_to_J_kg*tv%C_p * tv%T(i,j,k)) * &
720  (h(i,j,k)*(hl2_to_kg * areatm(i,j)))
721  enddo ; enddo ; enddo
722  salt_efp = reproducing_sum_efp(salt_int, isr, ier, jsr, jer, only_on_pe=.true.)
723  heat_efp = reproducing_sum_efp(temp_int, isr, ier, jsr, jer, only_on_pe=.true.)
724 
725  ! Combining the sums avoids multiple blocking all-PE updates.
726  efp_list(1) = salt_efp ; efp_list(2) = heat_efp ; efp_list(3) = cs%fresh_water_in_EFP
727  efp_list(4) = cs%net_salt_in_EFP ; efp_list(5) = cs%net_heat_in_EFP
728  call efp_sum_across_pes(efp_list, 5)
729  ! Return the globally summed values to the original variables.
730  salt_efp = efp_list(1) ; heat_efp = efp_list(2) ; cs%fresh_water_in_EFP = efp_list(3)
731  cs%net_salt_in_EFP = efp_list(4) ; cs%net_heat_in_EFP = efp_list(5)
732 
733  salt = efp_to_real(salt_efp)
734  heat = efp_to_real(heat_efp)
735  else
736  call efp_sum_across_pes(cs%fresh_water_in_EFP)
737  endif
738 
739 ! Calculate the maximum CFL numbers.
740  max_cfl(1:2) = 0.0
741  do k=1,nz ; do j=js,je ; do i=isq,ieq
742  cfl_iarea = g%IareaT(i,j)
743  if (u(i,j,k) < 0.0) &
744  cfl_iarea = g%IareaT(i+1,j)
745 
746  cfl_trans = abs(u(i,j,k) * cs%dt_in_T) * (g%dy_Cu(i,j) * cfl_iarea)
747  cfl_lin = abs(u(i,j,k) * cs%dt_in_T) * g%IdxCu(i,j)
748  max_cfl(1) = max(max_cfl(1), cfl_trans)
749  max_cfl(2) = max(max_cfl(2), cfl_lin)
750  enddo ; enddo ; enddo
751  do k=1,nz ; do j=jsq,jeq ; do i=is,ie
752  cfl_iarea = g%IareaT(i,j)
753  if (v(i,j,k) < 0.0) &
754  cfl_iarea = g%IareaT(i,j+1)
755 
756  cfl_trans = abs(v(i,j,k) * cs%dt_in_T) * (g%dx_Cv(i,j) * cfl_iarea)
757  cfl_lin = abs(v(i,j,k) * cs%dt_in_T) * g%IdyCv(i,j)
758  max_cfl(1) = max(max_cfl(1), cfl_trans)
759  max_cfl(2) = max(max_cfl(2), cfl_lin)
760  enddo ; enddo ; enddo
761 
762  call sum_across_pes(cs%ntrunc)
763  ! Sum the various quantities across all the processors. This sum is NOT
764  ! guaranteed to be bitwise reproducible, even on the same decomposition.
765  ! The sum of Tr_stocks should be reimplemented using the reproducing sums.
766  if (ntr_stocks > 0) call sum_across_pes(tr_stocks,ntr_stocks)
767 
768  call max_across_pes(max_cfl, 2)
769  irho0 = 1.0 / (us%R_to_kg_m3*gv%Rho0)
770 
771  if (cs%use_temperature) then
772  if (cs%previous_calls == 0) then
773  cs%salt_prev_EFP = salt_efp ; cs%heat_prev_EFP = heat_efp
774  endif
775  salt_chg_efp = salt_efp - cs%salt_prev_EFP
776  salt_chg = efp_to_real(salt_chg_efp)
777  salt_anom_efp = salt_chg_efp - cs%net_salt_in_EFP
778  salt_anom = efp_to_real(salt_anom_efp)
779  heat_chg_efp = heat_efp - cs%heat_prev_EFP
780  heat_chg = efp_to_real(heat_chg_efp)
781  heat_anom_efp = heat_chg_efp - cs%net_heat_in_EFP
782  heat_anom = efp_to_real(heat_anom_efp)
783  endif
784 
785  mass_chg_efp = mass_efp - cs%mass_prev_EFP
786  mass_anom_efp = mass_chg_efp - cs%fresh_water_in_EFP
787  mass_anom = efp_to_real(mass_anom_efp)
788  if (cs%use_temperature .and. .not.gv%Boussinesq) then
789  ! net_salt_input needs to be converted from ppt m s-1 to kg m-2 s-1.
790  mass_anom = mass_anom - 0.001*efp_to_real(cs%net_salt_in_EFP)
791  endif
792  mass_chg = efp_to_real(mass_chg_efp)
793 
794  if (cs%use_temperature) then
795  salin = salt / mass_tot ; salin_anom = salt_anom / mass_tot
796  ! salin_chg = Salt_chg / mass_tot
797  temp = heat / (mass_tot*us%Q_to_J_kg*tv%C_p) ; temp_anom = heat_anom / (mass_tot*us%Q_to_J_kg*tv%C_p)
798  endif
799  en_mass = toten / mass_tot
800 
801  call get_time(day, start_of_day, num_days)
802  date_stamped = (cs%date_stamped_output .and. (get_calendar_type() /= no_calendar))
803  if (date_stamped) &
804  call get_date(day, iyear, imonth, iday, ihour, iminute, isecond, itick)
805  if (abs(cs%timeunit - 86400.0) < 1.0) then
806  reday = REAL(num_days)+ (REAL(start_of_day)/86400.0)
807  mesg_intro = "MOM Day "
808  else
809  reday = REAL(num_days)*(86400.0/cs%timeunit) + &
810  REAL(start_of_day)/abs(cs%timeunit)
811  mesg_intro = "MOM Time "
812  endif
813  if (reday < 1.0e8) then ; write(day_str, '(F12.3)') reday
814  elseif (reday < 1.0e11) then ; write(day_str, '(F15.3)') reday
815  else ; write(day_str, '(ES15.9)') reday ; endif
816 
817  if (n < 1000000) then ; write(n_str, '(I6)') n
818  elseif (n < 10000000) then ; write(n_str, '(I7)') n
819  elseif (n < 100000000) then ; write(n_str, '(I8)') n
820  else ; write(n_str, '(I10)') n ; endif
821 
822  if (date_stamped) then
823  write(date_str,'("MOM Date",i7,2("/",i2.2)," ",i2.2,2(":",i2.2))') &
824  iyear, imonth, iday, ihour, iminute, isecond
825  else
826  date_str = trim(mesg_intro)//trim(day_str)
827  endif
828 
829  if (is_root_pe()) then
830  if (cs%use_temperature) then
831  write(*,'(A," ",A,": En ",ES12.6, ", MaxCFL ", F8.5, ", Mass ", &
832  & ES18.12, ", Salt ", F15.11,", Temp ", F15.11)') &
833  trim(date_str), trim(n_str), en_mass, max_cfl(1), mass_tot, salin, temp
834  else
835  write(*,'(A," ",A,": En ",ES12.6, ", MaxCFL ", F8.5, ", Mass ", &
836  & ES18.12)') &
837  trim(date_str), trim(n_str), en_mass, max_cfl(1), mass_tot
838  endif
839 
840  if (cs%use_temperature) then
841  write(cs%fileenergy_ascii,'(A,",",A,",", I6,", En ",ES22.16, &
842  &", CFL ", F8.5, ", SL ",&
843  &es11.4,", M ",ES11.5,", S",f8.4,", T",f8.4,&
844  &", Me ",ES9.2,", Se ",ES9.2,", Te ",ES9.2)') &
845  trim(n_str), trim(day_str), cs%ntrunc, en_mass, max_cfl(1), &
846  -h_0ape(1), mass_tot, salin, temp, mass_anom/mass_tot, salin_anom, &
847  temp_anom
848  else
849  write(cs%fileenergy_ascii,'(A,",",A,",", I6,", En ",ES22.16, &
850  &", CFL ", F8.5, ", SL ",&
851  &ES11.4,", Mass ",ES11.5,", Me ",ES9.2)') &
852  trim(n_str), trim(day_str), cs%ntrunc, en_mass, max_cfl(1), &
853  -h_0ape(1), mass_tot, mass_anom/mass_tot
854  endif
855 
856  if (cs%ntrunc > 0) then
857  write(*,'(A," Energy/Mass:",ES12.5," Truncations ",I0)') &
858  trim(date_str), en_mass, cs%ntrunc
859  endif
860 
861  if (cs%write_stocks) then
862  write(*,'(" Total Energy: ",Z16.16,ES24.16)') toten, toten
863  write(*,'(" Total Mass: ",ES24.16,", Change: ",ES24.16," Error: ",ES12.5," (",ES8.1,")")') &
864  mass_tot, mass_chg, mass_anom, mass_anom/mass_tot
865  if (cs%use_temperature) then
866  if (salt == 0.) then
867  write(*,'(" Total Salt: ",ES24.16,", Change: ",ES24.16," Error: ",ES12.5)') &
868  salt*0.001, salt_chg*0.001, salt_anom*0.001
869  else
870  write(*,'(" Total Salt: ",ES24.16,", Change: ",ES24.16," Error: ",ES12.5," (",ES8.1,")")') &
871  salt*0.001, salt_chg*0.001, salt_anom*0.001, salt_anom/salt
872  endif
873  if (heat == 0.) then
874  write(*,'(" Total Heat: ",ES24.16,", Change: ",ES24.16," Error: ",ES12.5)') &
875  heat, heat_chg, heat_anom
876  else
877  write(*,'(" Total Heat: ",ES24.16,", Change: ",ES24.16," Error: ",ES12.5," (",ES8.1,")")') &
878  heat, heat_chg, heat_anom, heat_anom/heat
879  endif
880  endif
881  do m=1,ntr_stocks
882 
883  write(*,'(" Total ",a,": ",ES24.16,X,a)') &
884  trim(tr_names(m)), tr_stocks(m), trim(tr_units(m))
885 
886  if (tr_minmax_got(m)) then
887  write(*,'(64X,"Global Min:",ES24.16,X,"at: (", f7.2,","f7.2,","f8.2,")" )') &
888  tr_min(m),tr_min_x(m),tr_min_y(m),tr_min_z(m)
889  write(*,'(64X,"Global Max:",ES24.16,X,"at: (", f7.2,","f7.2,","f8.2,")" )') &
890  tr_max(m),tr_max_x(m),tr_max_y(m),tr_max_z(m)
891  endif
892 
893  enddo
894  endif
895  endif
896 
897  var = real(cs%ntrunc)
898  call write_field(cs%fileenergy_nc, cs%fields(1), var, reday)
899  call write_field(cs%fileenergy_nc, cs%fields(2), toten, reday)
900  call write_field(cs%fileenergy_nc, cs%fields(3), pe, reday)
901  call write_field(cs%fileenergy_nc, cs%fields(4), ke, reday)
902  call write_field(cs%fileenergy_nc, cs%fields(5), h_0ape, reday)
903  call write_field(cs%fileenergy_nc, cs%fields(6), mass_lay, reday)
904 
905  call write_field(cs%fileenergy_nc, cs%fields(7), mass_tot, reday)
906  call write_field(cs%fileenergy_nc, cs%fields(8), mass_chg, reday)
907  call write_field(cs%fileenergy_nc, cs%fields(9), mass_anom, reday)
908  call write_field(cs%fileenergy_nc, cs%fields(10), max_cfl(1), reday)
909  call write_field(cs%fileenergy_nc, cs%fields(11), max_cfl(2), reday)
910  if (cs%use_temperature) then
911  call write_field(cs%fileenergy_nc, cs%fields(12), 0.001*salt, reday)
912  call write_field(cs%fileenergy_nc, cs%fields(13), 0.001*salt_chg, reday)
913  call write_field(cs%fileenergy_nc, cs%fields(14), 0.001*salt_anom, reday)
914  call write_field(cs%fileenergy_nc, cs%fields(15), heat, reday)
915  call write_field(cs%fileenergy_nc, cs%fields(16), heat_chg, reday)
916  call write_field(cs%fileenergy_nc, cs%fields(17), heat_anom, reday)
917  do m=1,ntr_stocks
918  call write_field(cs%fileenergy_nc, cs%fields(17+m), tr_stocks(m), reday)
919  enddo
920  else
921  do m=1,ntr_stocks
922  call write_field(cs%fileenergy_nc, cs%fields(11+m), tr_stocks(m), reday)
923  enddo
924  endif
925 
926  call flush_file(cs%fileenergy_nc)
927 
928  ! The second (impossible-looking) test looks for a NaN in En_mass.
929  if ((en_mass>cs%max_Energy) .or. &
930  ((en_mass>cs%max_Energy) .and. (en_mass<cs%max_Energy))) then
931  write(mesg,'("Energy per unit mass of ",ES11.4," exceeds ",ES11.4)') &
932  en_mass, cs%max_Energy
933  call mom_error(fatal, &
934  "write_energy : Excessive energy per unit mass or NaNs forced model termination.")
935  endif
936  if (cs%ntrunc>cs%maxtrunc) then
937  call mom_error(fatal, "write_energy : Ocean velocity has been truncated too many times.")
938  endif
939  cs%ntrunc = 0
940  cs%previous_calls = cs%previous_calls + 1
941 
942  cs%mass_prev_EFP = mass_efp ; cs%fresh_water_in_EFP = real_to_efp(0.0)
943  if (cs%use_temperature) then
944  cs%salt_prev_EFP = salt_efp ; cs%net_salt_in_EFP = real_to_efp(0.0)
945  cs%heat_prev_EFP = heat_efp ; cs%net_heat_in_EFP = real_to_efp(0.0)
946  endif
947 
948 end subroutine write_energy
949 
950 !> This subroutine accumates the net input of volume, salt and heat, through
951 !! the ocean surface for use in diagnosing conservation.
952 subroutine accumulate_net_input(fluxes, sfc_state, tv, dt, G, US, CS)
953  type(forcing), intent(in) :: fluxes !< A structure containing pointers to any possible
954  !! forcing fields. Unused fields are unallocated.
955  type(surface), intent(in) :: sfc_state !< A structure containing fields that
956  !! describe the surface state of the ocean.
957  type(thermo_var_ptrs), intent(in) :: tv !< A structure pointing to various
958  !! thermodynamic variables.
959  real, intent(in) :: dt !< The amount of time over which to average [T ~> s].
960  type(ocean_grid_type), intent(in) :: g !< The ocean's grid structure.
961  type(unit_scale_type), intent(in) :: us !< A dimensional unit scaling type
962  type(sum_output_cs), pointer :: cs !< The control structure returned by a previous call
963  !! to MOM_sum_output_init.
964  ! Local variables
965  real, dimension(SZI_(G),SZJ_(G)) :: &
966  fw_in, & ! The net fresh water input, integrated over a timestep [kg].
967  salt_in, & ! The total salt added by surface fluxes, integrated
968  ! over a time step [ppt kg].
969  heat_in ! The total heat added by surface fluxes, integrated
970  ! over a time step [J].
971  real :: fw_input ! The net fresh water input, integrated over a timestep
972  ! and summed over space [kg].
973  real :: salt_input ! The total salt added by surface fluxes, integrated
974  ! over a time step and summed over space [ppt kg].
975  real :: heat_input ! The total heat added by boundary fluxes, integrated
976  ! over a time step and summed over space [J].
977  real :: rzl2_to_kg ! A combination of scaling factors for mass [kg R-1 Z-1 L-2 ~> 1]
978  real :: qrzl2_to_j ! A combination of scaling factors for heat [J Q-1 R-1 Z-1 L-2 ~> 1]
979 
980  type(efp_type) :: &
981  fw_in_efp, & ! The net fresh water input, integrated over a timestep
982  ! and summed over space [kg].
983  salt_in_efp, & ! The total salt added by surface fluxes, integrated
984  ! over a time step and summed over space [ppt kg].
985  heat_in_efp ! The total heat added by boundary fluxes, integrated
986  ! over a time step and summed over space [J].
987 
988  real :: inputs(3) ! A mixed array for combining the sums
989  integer :: i, j, is, ie, js, je, isr, ier, jsr, jer
990 
991  is = g%isc ; ie = g%iec ; js = g%jsc ; je = g%jec
992 
993  rzl2_to_kg = us%L_to_m**2*us%RZ_to_kg_m2
994  qrzl2_to_j = rzl2_to_kg*us%Q_to_J_kg
995 
996  fw_in(:,:) = 0.0
997  if (associated(fluxes%evap)) then
998  if (associated(fluxes%lprec) .and. associated(fluxes%fprec)) then
999  do j=js,je ; do i=is,ie
1000  fw_in(i,j) = rzl2_to_kg * dt*g%areaT(i,j)*(fluxes%evap(i,j) + &
1001  (((fluxes%lprec(i,j) + fluxes%vprec(i,j)) + fluxes%lrunoff(i,j)) + &
1002  (fluxes%fprec(i,j) + fluxes%frunoff(i,j))))
1003  enddo ; enddo
1004  else
1005  call mom_error(warning, &
1006  "accumulate_net_input called with associated evap field, but no precip field.")
1007  endif
1008  endif
1009 
1010  if (associated(fluxes%seaice_melt)) then ; do j=js,je ; do i=is,ie
1011  fw_in(i,j) = fw_in(i,j) + rzl2_to_kg*dt * &
1012  g%areaT(i,j) * fluxes%seaice_melt(i,j)
1013  enddo ; enddo ; endif
1014 
1015  salt_in(:,:) = 0.0 ; heat_in(:,:) = 0.0
1016  if (cs%use_temperature) then
1017 
1018  if (associated(fluxes%sw)) then ; do j=js,je ; do i=is,ie
1019  heat_in(i,j) = heat_in(i,j) + dt * qrzl2_to_j * g%areaT(i,j) * (fluxes%sw(i,j) + &
1020  (fluxes%lw(i,j) + (fluxes%latent(i,j) + fluxes%sens(i,j))))
1021  enddo ; enddo ; endif
1022 
1023  if (associated(fluxes%seaice_melt_heat)) then ; do j=js,je ; do i=is,ie
1024  heat_in(i,j) = heat_in(i,j) + dt * qrzl2_to_j * g%areaT(i,j) * &
1025  fluxes%seaice_melt_heat(i,j)
1026  enddo ; enddo ; endif
1027 
1028  ! smg: new code
1029  ! include heat content from water transport across ocean surface
1030 ! if (associated(fluxes%heat_content_lprec)) then ; do j=js,je ; do i=is,ie
1031 ! heat_in(i,j) = heat_in(i,j) + dt * QRZL2_to_J * G%areaT(i,j) * &
1032 ! (fluxes%heat_content_lprec(i,j) + (fluxes%heat_content_fprec(i,j) &
1033 ! + (fluxes%heat_content_lrunoff(i,j) + (fluxes%heat_content_frunoff(i,j) &
1034 ! + (fluxes%heat_content_cond(i,j) + (fluxes%heat_content_vprec(i,j) &
1035 ! + fluxes%heat_content_massout(i,j)))))))
1036 ! enddo ; enddo ; endif
1037 
1038  ! smg: old code
1039  if (associated(tv%TempxPmE)) then
1040  do j=js,je ; do i=is,ie
1041  heat_in(i,j) = heat_in(i,j) + (fluxes%C_p * qrzl2_to_j*g%areaT(i,j)) * tv%TempxPmE(i,j)
1042  enddo ; enddo
1043  elseif (associated(fluxes%evap)) then
1044  do j=js,je ; do i=is,ie
1045  heat_in(i,j) = heat_in(i,j) + (us%Q_to_J_kg*fluxes%C_p * sfc_state%SST(i,j)) * fw_in(i,j)
1046  enddo ; enddo
1047  endif
1048 
1049  ! The following heat sources may or may not be used.
1050  if (associated(tv%internal_heat)) then
1051  do j=js,je ; do i=is,ie
1052  heat_in(i,j) = heat_in(i,j) + (fluxes%C_p * qrzl2_to_j*g%areaT(i,j)) * tv%internal_heat(i,j)
1053  enddo ; enddo
1054  endif
1055  if (associated(tv%frazil)) then ; do j=js,je ; do i=is,ie
1056  heat_in(i,j) = heat_in(i,j) + qrzl2_to_j * g%areaT(i,j) * tv%frazil(i,j)
1057  enddo ; enddo ; endif
1058  if (associated(fluxes%heat_added)) then ; do j=js,je ; do i=is,ie
1059  heat_in(i,j) = heat_in(i,j) + qrzl2_to_j * dt*g%areaT(i,j) * fluxes%heat_added(i,j)
1060  enddo ; enddo ; endif
1061 ! if (associated(sfc_state%sw_lost)) then ; do j=js,je ; do i=is,ie
1062 ! heat_in(i,j) = heat_in(i,j) - US%L_to_m**2*G%areaT(i,j) * sfc_state%sw_lost(i,j)
1063 ! enddo ; enddo ; endif
1064 
1065  if (associated(fluxes%salt_flux)) then ; do j=js,je ; do i=is,ie
1066  ! convert salt_flux from kg (salt)/(m^2 s) to ppt * [m s-1].
1067  salt_in(i,j) = rzl2_to_kg * dt * &
1068  g%areaT(i,j)*(1000.0*fluxes%salt_flux(i,j))
1069  enddo ; enddo ; endif
1070  endif
1071 
1072  if ((cs%use_temperature) .or. associated(fluxes%lprec) .or. &
1073  associated(fluxes%evap)) then
1074  ! The on-PE sums are stored here, but the sums across PEs are deferred to
1075  ! the next call to write_energy to avoid extra barriers.
1076  isr = is - (g%isd-1) ; ier = ie - (g%isd-1) ; jsr = js - (g%jsd-1) ; jer = je - (g%jsd-1)
1077  fw_in_efp = reproducing_sum_efp(fw_in, isr, ier, jsr, jer, only_on_pe=.true.)
1078  heat_in_efp = reproducing_sum_efp(heat_in, isr, ier, jsr, jer, only_on_pe=.true.)
1079  salt_in_efp = reproducing_sum_efp(salt_in, isr, ier, jsr, jer, only_on_pe=.true.)
1080 
1081  cs%fresh_water_in_EFP = cs%fresh_water_in_EFP + fw_in_efp
1082  cs%net_salt_in_EFP = cs%net_salt_in_EFP + salt_in_efp
1083  cs%net_heat_in_EFP = cs%net_heat_in_EFP + heat_in_efp
1084  endif
1085 
1086 end subroutine accumulate_net_input
1087 
1088 !> This subroutine sets up an ordered list of depths, along with the
1089 !! cross sectional areas at each depth and the volume of fluid deeper
1090 !! than each depth. This might be read from a previously created file
1091 !! or it might be created anew. (For now only new creation occurs.
1092 subroutine depth_list_setup(G, US, CS)
1093  type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure
1094  type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
1095  type(Sum_output_CS), pointer :: CS !< The control structure returned by a
1096  !! previous call to MOM_sum_output_init.
1097  ! Local variables
1098  integer :: k
1099 
1100  if (cs%read_depth_list) then
1101  if (file_exists(cs%depth_list_file)) then
1102  call read_depth_list(g, us, cs, cs%depth_list_file)
1103  else
1104  if (is_root_pe()) call mom_error(warning, "depth_list_setup: "// &
1105  trim(cs%depth_list_file)//" does not exist. Creating a new file.")
1106  call create_depth_list(g, cs)
1107 
1108  call write_depth_list(g, us, cs, cs%depth_list_file, cs%list_size+1)
1109  endif
1110  else
1111  call create_depth_list(g, cs)
1112  endif
1113 
1114  do k=1,g%ke
1115  cs%lH(k) = cs%list_size
1116  enddo
1117 
1118 end subroutine depth_list_setup
1119 
1120 !> create_depth_list makes an ordered list of depths, along with the cross
1121 !! sectional areas at each depth and the volume of fluid deeper than each depth.
1122 subroutine create_depth_list(G, CS)
1123  type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure.
1124  type(Sum_output_CS), pointer :: CS !< The control structure set up in MOM_sum_output_init,
1125  !! in which the ordered depth list is stored.
1126  ! Local variables
1127  real, dimension(G%Domain%niglobal*G%Domain%njglobal + 1) :: &
1128  Dlist, & !< The global list of bottom depths [Z ~> m].
1129  AreaList !< The global list of cell areas [L2 ~> m2].
1130  integer, dimension(G%Domain%niglobal*G%Domain%njglobal+1) :: &
1131  indx2 !< The position of an element in the original unsorted list.
1132  real :: Dnow !< The depth now being considered for sorting [Z ~> m].
1133  real :: Dprev !< The most recent depth that was considered [Z ~> m].
1134  real :: vol !< The running sum of open volume below a deptn [Z L2 ~> m3].
1135  real :: area !< The open area at the current depth [L2 ~> m2].
1136  real :: D_list_prev !< The most recent depth added to the list [Z ~> m].
1137  logical :: add_to_list !< This depth should be included as an entry on the list.
1138 
1139  integer :: ir, indxt
1140  integer :: mls, list_size
1141  integer :: list_pos, i_global, j_global
1142  integer :: i, j, k, kl
1143 
1144  mls = g%Domain%niglobal*g%Domain%njglobal
1145 
1146 ! Need to collect the global data from compute domains to a 1D array for sorting.
1147  dlist(:) = 0.0
1148  arealist(:) = 0.0
1149  do j=g%jsc,g%jec ; do i=g%isc,g%iec
1150  ! Set global indices that start the global domain at 1 (Fortran convention).
1151  j_global = j + g%jdg_offset - (g%jsg-1)
1152  i_global = i + g%idg_offset - (g%isg-1)
1153 
1154  list_pos = (j_global-1)*g%Domain%niglobal + i_global
1155  dlist(list_pos) = g%bathyT(i,j)
1156  arealist(list_pos) = g%mask2dT(i,j) * g%areaT(i,j)
1157  enddo ; enddo
1158 
1159  ! These sums reproduce across PEs because the arrays are only nonzero on one PE.
1160  call sum_across_pes(dlist, mls+1)
1161  call sum_across_pes(arealist, mls+1)
1162 
1163  do j=1,mls+1 ; indx2(j) = j ; enddo
1164  k = mls / 2 + 1 ; ir = mls
1165  do
1166  if (k > 1) then
1167  k = k - 1
1168  indxt = indx2(k)
1169  dnow = dlist(indxt)
1170  else
1171  indxt = indx2(ir)
1172  dnow = dlist(indxt)
1173  indx2(ir) = indx2(1)
1174  ir = ir - 1
1175  if (ir == 1) then ; indx2(1) = indxt ; exit ; endif
1176  endif
1177  i=k ; j=k*2
1178  do ; if (j > ir) exit
1179  if (j < ir .AND. dlist(indx2(j)) < dlist(indx2(j+1))) j = j + 1
1180  if (dnow < dlist(indx2(j))) then ; indx2(i) = indx2(j) ; i = j ; j = j + i
1181  else ; j = ir+1 ; endif
1182  enddo
1183  indx2(i) = indxt
1184  enddo
1185 
1186 ! At this point, the lists should perhaps be culled to save memory.
1187 ! Culling: (1) identical depths (e.g. land) - take the last one.
1188 ! (2) the topmost and bottommost depths are always saved.
1189 ! (3) very close depths
1190 ! (4) equal volume changes.
1191 
1192  ! Count the unique elements in the list.
1193  d_list_prev = dlist(indx2(mls))
1194  list_size = 2
1195  do k=mls-1,1,-1
1196  if (dlist(indx2(k)) < d_list_prev-cs%D_list_min_inc) then
1197  list_size = list_size + 1
1198  d_list_prev = dlist(indx2(k))
1199  endif
1200  enddo
1201 
1202  cs%list_size = list_size
1203  allocate(cs%DL(cs%list_size+1))
1204 
1205  vol = 0.0 ; area = 0.0
1206  dprev = dlist(indx2(mls))
1207  d_list_prev = dprev
1208 
1209  kl = 0
1210  do k=mls,1,-1
1211  i = indx2(k)
1212  vol = vol + area * (dprev - dlist(i))
1213  area = area + arealist(i)
1214 
1215  add_to_list = .false.
1216  if ((kl == 0) .or. (k==1)) then
1217  add_to_list = .true.
1218  elseif (dlist(indx2(k-1)) < d_list_prev-cs%D_list_min_inc) then
1219  add_to_list = .true.
1220  d_list_prev = dlist(indx2(k-1))
1221  endif
1222 
1223  if (add_to_list) then
1224  kl = kl+1
1225  cs%DL(kl)%depth = dlist(i)
1226  cs%DL(kl)%area = area
1227  cs%DL(kl)%vol_below = vol
1228  endif
1229  dprev = dlist(i)
1230  enddo
1231 
1232  do while (kl < list_size)
1233  ! I don't understand why this is needed... RWH
1234  kl = kl+1
1235  cs%DL(kl)%vol_below = cs%DL(kl-1)%vol_below * 1.000001
1236  cs%DL(kl)%area = cs%DL(kl-1)%area
1237  cs%DL(kl)%depth = cs%DL(kl-1)%depth
1238  enddo
1239 
1240  cs%DL(cs%list_size+1)%vol_below = cs%DL(cs%list_size)%vol_below * 1000.0
1241  cs%DL(cs%list_size+1)%area = cs%DL(cs%list_size)%area
1242  cs%DL(cs%list_size+1)%depth = cs%DL(cs%list_size)%depth
1243 
1244 end subroutine create_depth_list
1245 
1246 !> This subroutine writes out the depth list to the specified file.
1247 subroutine write_depth_list(G, US, CS, filename, list_size)
1248  type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure.
1249  type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
1250  type(Sum_output_CS), pointer :: CS !< The control structure returned by a
1251  !! previous call to MOM_sum_output_init.
1252  character(len=*), intent(in) :: filename !< The path to the depth list file to write.
1253  integer, intent(in) :: list_size !< The size of the depth list.
1254  ! Local variables
1255  real, allocatable :: tmp(:)
1256  integer :: ncid, dimid(1), Did, Aid, Vid, status, k
1257  character(len=16) :: depth_chksum, area_chksum
1258 
1259  ! All ranks are required to compute the global checksum
1260  call get_depth_list_checksums(g, depth_chksum, area_chksum)
1261 
1262  if (.not.is_root_pe()) return
1263 
1264  allocate(tmp(list_size)) ; tmp(:) = 0.0
1265 
1266  status = nf90_create(filename, 0, ncid)
1267  if (status /= nf90_noerr) then
1268  call mom_error(warning, filename//trim(nf90_strerror(status)))
1269  return
1270  endif
1271 
1272  status = nf90_def_dim(ncid, "list", list_size, dimid(1))
1273  if (status /= nf90_noerr) call mom_error(warning, &
1274  filename//trim(nf90_strerror(status)))
1275 
1276  status = nf90_def_var(ncid, "depth", nf90_double, dimid, did)
1277  if (status /= nf90_noerr) call mom_error(warning, &
1278  filename//" depth "//trim(nf90_strerror(status)))
1279  status = nf90_put_att(ncid, did, "long_name", "Sorted depth")
1280  if (status /= nf90_noerr) call mom_error(warning, &
1281  filename//" depth "//trim(nf90_strerror(status)))
1282  status = nf90_put_att(ncid, did, "units", "m")
1283  if (status /= nf90_noerr) call mom_error(warning, &
1284  filename//" depth "//trim(nf90_strerror(status)))
1285 
1286  status = nf90_def_var(ncid, "area", nf90_double, dimid, aid)
1287  if (status /= nf90_noerr) call mom_error(warning, &
1288  filename//" area "//trim(nf90_strerror(status)))
1289  status = nf90_put_att(ncid, aid, "long_name", "Open area at depth")
1290  if (status /= nf90_noerr) call mom_error(warning, &
1291  filename//" area "//trim(nf90_strerror(status)))
1292  status = nf90_put_att(ncid, aid, "units", "m2")
1293  if (status /= nf90_noerr) call mom_error(warning, &
1294  filename//" area "//trim(nf90_strerror(status)))
1295 
1296  status = nf90_def_var(ncid, "vol_below", nf90_double, dimid, vid)
1297  if (status /= nf90_noerr) call mom_error(warning, &
1298  filename//" vol_below "//trim(nf90_strerror(status)))
1299  status = nf90_put_att(ncid, vid, "long_name", "Open volume below depth")
1300  if (status /= nf90_noerr) call mom_error(warning, &
1301  filename//" vol_below "//trim(nf90_strerror(status)))
1302  status = nf90_put_att(ncid, vid, "units", "m3")
1303  if (status /= nf90_noerr) call mom_error(warning, &
1304  filename//" vol_below "//trim(nf90_strerror(status)))
1305 
1306  ! Dependency checksums
1307  status = nf90_put_att(ncid, nf90_global, depth_chksum_attr, depth_chksum)
1308  if (status /= nf90_noerr) call mom_error(warning, &
1309  filename//" "//depth_chksum_attr//" "//trim(nf90_strerror(status)))
1310 
1311  status = nf90_put_att(ncid, nf90_global, area_chksum_attr, area_chksum)
1312  if (status /= nf90_noerr) call mom_error(warning, &
1313  filename//" "//area_chksum_attr//" "//trim(nf90_strerror(status)))
1314 
1315  status = nf90_enddef(ncid)
1316  if (status /= nf90_noerr) call mom_error(warning, &
1317  filename//trim(nf90_strerror(status)))
1318 
1319  do k=1,list_size ; tmp(k) = us%Z_to_m*cs%DL(k)%depth ; enddo
1320  status = nf90_put_var(ncid, did, tmp)
1321  if (status /= nf90_noerr) call mom_error(warning, &
1322  filename//" depth "//trim(nf90_strerror(status)))
1323 
1324  do k=1,list_size ; tmp(k) = us%L_to_m**2*cs%DL(k)%area ; enddo
1325  status = nf90_put_var(ncid, aid, tmp)
1326  if (status /= nf90_noerr) call mom_error(warning, &
1327  filename//" area "//trim(nf90_strerror(status)))
1328 
1329  do k=1,list_size ; tmp(k) = us%Z_to_m*us%L_to_m**2*cs%DL(k)%vol_below ; enddo
1330  status = nf90_put_var(ncid, vid, tmp)
1331  if (status /= nf90_noerr) call mom_error(warning, &
1332  filename//" vol_below "//trim(nf90_strerror(status)))
1333 
1334  status = nf90_close(ncid)
1335  if (status /= nf90_noerr) call mom_error(warning, &
1336  filename//trim(nf90_strerror(status)))
1337 
1338 end subroutine write_depth_list
1339 
1340 !> This subroutine reads in the depth list to the specified file
1341 !! and allocates and sets up CS%DL and CS%list_size .
1342 subroutine read_depth_list(G, US, CS, filename)
1343  type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure
1344  type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
1345  type(Sum_output_CS), pointer :: CS !< The control structure returned by a
1346  !! previous call to MOM_sum_output_init.
1347  character(len=*), intent(in) :: filename !< The path to the depth list file to read.
1348  ! Local variables
1349  character(len=32) :: mdl
1350  character(len=240) :: var_name, var_msg
1351  real, allocatable :: tmp(:)
1352  integer :: ncid, status, varid, list_size, k
1353  integer :: ndim, len, var_dim_ids(NF90_MAX_VAR_DIMS)
1354  character(len=16) :: depth_file_chksum, depth_grid_chksum
1355  character(len=16) :: area_file_chksum, area_grid_chksum
1356  integer :: depth_attr_status, area_attr_status
1357 
1358  mdl = "MOM_sum_output read_depth_list:"
1359 
1360  status = nf90_open(filename, nf90_nowrite, ncid)
1361  if (status /= nf90_noerr) then
1362  call mom_error(fatal,mdl//" Difficulties opening "//trim(filename)// &
1363  " - "//trim(nf90_strerror(status)))
1364  endif
1365 
1366  ! Check bathymetric consistency
1367  depth_attr_status = nf90_get_att(ncid, nf90_global, depth_chksum_attr, &
1368  depth_file_chksum)
1369  area_attr_status = nf90_get_att(ncid, nf90_global, area_chksum_attr, &
1370  area_file_chksum)
1371 
1372  if (any([depth_attr_status, area_attr_status] == nf90_enotatt)) then
1373  var_msg = trim(cs%depth_list_file) // " checksums are missing;"
1374  if (cs%require_depth_list_chksum) then
1375  call mom_error(fatal, trim(var_msg) // " aborting.")
1376  elseif (cs%update_depth_list_chksum) then
1377  call mom_error(warning, trim(var_msg) // " updating file.")
1378  call create_depth_list(g, cs)
1379  call write_depth_list(g, us, cs, cs%depth_list_file, cs%list_size+1)
1380  return
1381  else
1382  call mom_error(warning, &
1383  trim(var_msg) // " some diagnostics may not be reproducible.")
1384  endif
1385  else
1386  ! Validate netCDF call
1387  if (depth_attr_status /= nf90_noerr) then
1388  var_msg = mdl // "Failed to read " // trim(filename) // ":" &
1389  // depth_chksum_attr
1390  call mom_error(fatal, &
1391  trim(var_msg) // " - " // nf90_strerror(depth_attr_status))
1392  endif
1393 
1394  if (area_attr_status /= nf90_noerr) then
1395  var_msg = mdl // "Failed to read " // trim(filename) // ":" &
1396  // area_chksum_attr
1397  call mom_error(fatal, &
1398  trim(var_msg) // " - " // nf90_strerror(area_attr_status))
1399  endif
1400 
1401  call get_depth_list_checksums(g, depth_grid_chksum, area_grid_chksum)
1402 
1403  if (depth_grid_chksum /= depth_file_chksum &
1404  .or. area_grid_chksum /= area_file_chksum) then
1405  var_msg = trim(cs%depth_list_file) // " checksums do not match;"
1406  if (cs%require_depth_list_chksum) then
1407  call mom_error(fatal, trim(var_msg) // " aborting.")
1408  elseif (cs%update_depth_list_chksum) then
1409  call mom_error(warning, trim(var_msg) // " updating file.")
1410  call create_depth_list(g, cs)
1411  call write_depth_list(g, us, cs, cs%depth_list_file, cs%list_size+1)
1412  return
1413  else
1414  call mom_error(warning, &
1415  trim(var_msg) // " some diagnostics may not be reproducible.")
1416  endif
1417  endif
1418  endif
1419 
1420  var_name = "depth"
1421  var_msg = trim(var_name)//" in "//trim(filename)//" - "
1422  status = nf90_inq_varid(ncid, var_name, varid)
1423  if (status /= nf90_noerr) call mom_error(fatal,mdl// &
1424  " Difficulties finding variable "//trim(var_msg)//&
1425  trim(nf90_strerror(status)))
1426 
1427  status = nf90_inquire_variable(ncid, varid, ndims=ndim, dimids=var_dim_ids)
1428  if (status /= nf90_noerr) then
1429  call mom_error(fatal,mdl//" cannot inquire about "//trim(var_msg)//&
1430  trim(nf90_strerror(status)))
1431  elseif (ndim > 1) then
1432  call mom_error(fatal,mdl//" "//trim(var_msg)//&
1433  " has too many or too few dimensions.")
1434  endif
1435 
1436  ! Get the length of the list.
1437  status = nf90_inquire_dimension(ncid, var_dim_ids(1), len=list_size)
1438  if (status /= nf90_noerr) call mom_error(fatal,mdl// &
1439  " cannot inquire about dimension(1) of "//trim(var_msg)//&
1440  trim(nf90_strerror(status)))
1441 
1442  cs%list_size = list_size-1
1443  allocate(cs%DL(list_size))
1444  allocate(tmp(list_size))
1445 
1446  status = nf90_get_var(ncid, varid, tmp)
1447  if (status /= nf90_noerr) call mom_error(fatal,mdl// &
1448  " Difficulties reading variable "//trim(var_msg)//&
1449  trim(nf90_strerror(status)))
1450 
1451  do k=1,list_size ; cs%DL(k)%depth = us%m_to_Z*tmp(k) ; enddo
1452 
1453  var_name = "area"
1454  var_msg = trim(var_name)//" in "//trim(filename)//" - "
1455  status = nf90_inq_varid(ncid, var_name, varid)
1456  if (status /= nf90_noerr) call mom_error(fatal,mdl// &
1457  " Difficulties finding variable "//trim(var_msg)//&
1458  trim(nf90_strerror(status)))
1459  status = nf90_get_var(ncid, varid, tmp)
1460  if (status /= nf90_noerr) call mom_error(fatal,mdl// &
1461  " Difficulties reading variable "//trim(var_msg)//&
1462  trim(nf90_strerror(status)))
1463 
1464  do k=1,list_size ; cs%DL(k)%area = us%m_to_L**2*tmp(k) ; enddo
1465 
1466  var_name = "vol_below"
1467  var_msg = trim(var_name)//" in "//trim(filename)
1468  status = nf90_inq_varid(ncid, var_name, varid)
1469  if (status /= nf90_noerr) call mom_error(fatal,mdl// &
1470  " Difficulties finding variable "//trim(var_msg)//&
1471  trim(nf90_strerror(status)))
1472  status = nf90_get_var(ncid, varid, tmp)
1473  if (status /= nf90_noerr) call mom_error(fatal,mdl// &
1474  " Difficulties reading variable "//trim(var_msg)//&
1475  trim(nf90_strerror(status)))
1476 
1477  do k=1,list_size ; cs%DL(k)%vol_below = us%m_to_Z*us%m_to_L**2*tmp(k) ; enddo
1478 
1479  status = nf90_close(ncid)
1480  if (status /= nf90_noerr) call mom_error(warning, mdl// &
1481  " Difficulties closing "//trim(filename)//" - "//trim(nf90_strerror(status)))
1482 
1483  deallocate(tmp)
1484 
1485 end subroutine read_depth_list
1486 
1487 
1488 !> Return the checksums required to verify DEPTH_LIST_FILE contents.
1489 !!
1490 !! This function computes checksums for the bathymetry (G%bathyT) and masked
1491 !! area (mask2dT * areaT) fields of the model grid G, which are used to compute
1492 !! the depth list. A difference in checksum indicates that a different method
1493 !! was used to compute the grid data, and that any results using the depth
1494 !! list, such as APE, will not be reproducible.
1495 !!
1496 !! Checksums are saved as hexadecimal strings, in order to avoid potential
1497 !! datatype issues with netCDF attributes.
1498 subroutine get_depth_list_checksums(G, depth_chksum, area_chksum)
1499  type(ocean_grid_type), intent(in) :: G !< Ocean grid structure
1500  character(len=16), intent(out) :: depth_chksum !< Depth checksum hexstring
1501  character(len=16), intent(out) :: area_chksum !< Area checksum hexstring
1502 
1503  integer :: i, j
1504  real, allocatable :: field(:,:)
1505 
1506  allocate(field(g%isc:g%iec, g%jsc:g%jec))
1507 
1508  ! Depth checksum
1509  do j=g%jsc,g%jec ; do i=g%isc,g%iec
1510  field(i,j) = g%bathyT(i,j)
1511  enddo ; enddo
1512  write(depth_chksum, '(Z16)') mpp_chksum(field(:,:))
1513 
1514  ! Area checksum
1515  do j=g%jsc,g%jec ; do i=g%isc,g%iec
1516  field(i,j) = g%mask2dT(i,j) * g%US%L_to_m**2*g%areaT(i,j)
1517  enddo ; enddo
1518  write(area_chksum, '(Z16)') mpp_chksum(field(:,:))
1519 
1520  deallocate(field)
1521 end subroutine get_depth_list_checksums
1522 
1523 !> \namespace mom_sum_output
1524 !!
1525 !! By Robert Hallberg, April 1994 - June 2002
1526 !!
1527 !! This file contains the subroutine (write_energy) that writes
1528 !! horizontally integrated quantities, such as energies and layer
1529 !! volumes, and other summary information to an output file. Some
1530 !! of these quantities (APE or resting interface height) are defined
1531 !! relative to the global histogram of topography. The subroutine
1532 !! that compiles that histogram (depth_list_setup) is also included
1533 !! in this file.
1534 !!
1535 !! In addition, if the number of velocity truncations since the
1536 !! previous call to write_energy exceeds maxtrunc or the total energy
1537 !! exceeds a very large threshold, a fatal termination is triggered.
1538 
1539 end module mom_sum_output
Calculates the heights of sruface or all interfaces from layer thicknesses.
Pointers to various fields which may be used describe the surface state of MOM, and which will be ret...
Wraps the FMS time manager functions.
This module implements boundary forcing for MOM6.
Reports integrated quantities for monitoring the model state.
Functions for calculating interface heights, including free surface height.
Ocean grid type. See mom_grid for details.
Definition: MOM_grid.F90:26
Sum a value or 1-d array of values across processors, returning the sums in place.
Definition: MOM_coms.F90:64
A structure that can be parsed to read and document run-time parameters.
Provides the ocean grid type.
Definition: MOM_grid.F90:2
Open boundary segment data structure.
A list of depths and corresponding globally integrated ocean area at each depth and the ocean volume ...
This module contains I/O framework code.
Definition: MOM_io.F90:2
The MOM6 facility to parse input files for runtime parameters.
An overloaded interface to log the values of various types of parameters.
Orchestrates the registration and calling of tracer packages.
Interfaces to non-domain-oriented communication subroutines, including the MOM6 reproducing sums faci...
Definition: MOM_coms.F90:3
The control structure for the MOM_sum_output module.
Describes various unit conversion factors.
Provides a transparent unit rescaling type to facilitate dimensional consistency testing.
Routines for error handling and I/O management.
The control structure for orchestrating the calling of tracer packages.
Structure that contains pointers to the boundary forcing used to drive the liquid ocean simulated by ...
Type for describing a variable, typically a tracer.
Definition: MOM_io.F90:53
An overloaded interface to log version information about modules.
Describes the vertical ocean grid, including unit conversion factors.
Indicate whether a file exists, perhaps with domain decomposition.
Definition: MOM_io.F90:68
Pointers to an assortment of thermodynamic fields that may be available, including potential temperat...
Find an accurate and order-invariant sum of a distributed 2d or 3d field.
Definition: MOM_coms.F90:53
Find an accurate and order-invariant sum of a distributed 2d field, returning the result in the form ...
Definition: MOM_coms.F90:59
Controls where open boundary conditions are applied.
Provides a transparent vertical ocean grid type and supporting routines.
Provides transparent structures with groups of MOM6 variables and supporting routines.
An overloaded interface to read and log the values of various types of parameters.
The Extended Fixed Point (EFP) type provides a public interface for doing sums and taking differences...
Definition: MOM_coms.F90:74