← back to index

src/core/MOM_open_boundary.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!> Controls where open boundary conditions are applied
6module MOM_open_boundary
7
8use MOM_array_transform, only : rotate_array, rotate_array_pair
9use MOM_coms, only : sum_across_PEs, any_across_PEs
10use MOM_coms, only : Set_PElist, Get_PElist, PE_here, num_PEs
11use MOM_cpu_clock, only : cpu_clock_id, cpu_clock_begin, cpu_clock_end, CLOCK_ROUTINE
12use MOM_debugging, only : hchksum, uvchksum, chksum
13use MOM_diag_mediator, only : diag_ctrl, time_type
14use MOM_domains, only : pass_var, pass_vector
15use MOM_domains, only : create_group_pass, do_group_pass, group_pass_type
16use MOM_domains, only : To_All, EAST_FACE, NORTH_FACE, SCALAR_PAIR, CGRID_NE, CORNER
17use MOM_dyn_horgrid, only : dyn_horgrid_type
18use MOM_error_handler, only : MOM_mesg, MOM_error, FATAL, WARNING, NOTE, is_root_pe
19use MOM_file_parser, only : get_param, log_version, param_file_type, read_param
20use MOM_grid, only : ocean_grid_type, hor_index_type
21use MOM_interface_heights, only : thickness_to_dz
22use MOM_interpolate, only : init_external_field, time_interp_external, time_interp_external_init
23use MOM_interpolate, only : external_field
24use MOM_io, only : slasher, field_size, file_exists, stderr, SINGLE_FILE
25use MOM_io, only : vardesc, query_vardesc, var_desc
26use MOM_regridding, only : regridding_CS
27use MOM_remapping, only : remappingSchemesDoc, remappingDefaultScheme, remapping_CS
28use MOM_remapping, only : initialize_remapping, remapping_core_h, end_remapping
29use MOM_restart, only : register_restart_field, register_restart_pair
30use MOM_restart, only : query_initialized, set_initialized, MOM_restart_CS
31use MOM_string_functions, only : extract_word, remove_spaces, uppercase, lowercase
32use MOM_tidal_forcing, only : astro_longitudes, astro_longitudes_init, eq_phase, nodal_fu, tidal_frequency
33use MOM_time_manager, only : set_date, time_type, time_minus_signed
34use MOM_tracer_registry, only : tracer_type, tracer_registry_type, tracer_name_lookup
35use MOM_unit_scaling, only : unit_scale_type
36use MOM_variables, only : thermo_var_ptrs
37use MOM_verticalGrid, only : verticalGrid_type
38
39implicit none ; private
40
41#include <MOM_memory.h>
42
43public open_boundary_apply_normal_flow
44public open_boundary_config
45public open_boundary_setup_vert
46public open_boundary_halo_update
47public open_boundary_query
48public open_boundary_end
49public open_boundary_impose_normal_slope
50public open_boundary_impose_land_mask
51public radiation_open_bdry_conds
52public read_OBC_segment_data
53public update_OBC_segment_data
54public initialize_OBC_segment_reservoirs
55public open_boundary_test_extern_uv
56public open_boundary_test_extern_h
57public open_boundary_zero_normal_flow
58public parse_segment_str
59public register_OBC, OBC_registry_init
60public register_file_OBC, file_OBC_end
61public segment_tracer_registry_init
62public segment_tracer_registry_end
63public segment_thickness_reservoir_init
64public register_segment_tracer
65public register_temp_salt_segments
66public register_obgc_segments
67public fill_temp_salt_segments
68public fill_obgc_segments
69public fill_thickness_segments
70public set_obgc_segments_props
71public setup_OBC_tracer_reservoirs
72public setup_OBC_thickness_reservoirs
73public open_boundary_register_restarts
74public copy_thickness_reservoirs
75public update_segment_tracer_reservoirs
76public update_segment_thickness_reservoirs
77public set_initialized_OBC_tracer_reservoirs
78public update_OBC_ramp
79public remap_OBC_fields
80public rotate_OBC_config
81public rotate_OBC_segment_direction
82public write_OBC_info, chksum_OBC_segments
83public initialize_segment_data
84public flood_fill
85public flood_fill2
86
87integer, parameter, public :: OBC_NONE = 0 !< Indicates the use of no open boundary
88integer, parameter, public :: OBC_DIRECTION_N = 100 !< Indicates the boundary is an effective northern boundary
89integer, parameter, public :: OBC_DIRECTION_S = 200 !< Indicates the boundary is an effective southern boundary
90integer, parameter, public :: OBC_DIRECTION_E = 300 !< Indicates the boundary is an effective eastern boundary
91integer, parameter, public :: OBC_DIRECTION_W = 400 !< Indicates the boundary is an effective western boundary
92!>@{ Enumeration values for OBC relative vorticity configurations
93integer, parameter, public :: OBC_VORTICITY_NONE = 0
94integer, parameter, public :: OBC_VORTICITY_ZERO = 1
95integer, parameter, public :: OBC_VORTICITY_FREESLIP = 2
96integer, parameter, public :: OBC_VORTICITY_COMPUTED = 3
97integer, parameter, public :: OBC_VORTICITY_SPECIFIED = 4
98!>@}
99!>@{ Enumeration values for OBC strain configurations
100integer, parameter, public :: OBC_STRAIN_NONE = 0
101integer, parameter, public :: OBC_STRAIN_ZERO = 1
102integer, parameter, public :: OBC_STRAIN_FREESLIP = 2
103integer, parameter, public :: OBC_STRAIN_COMPUTED = 3
104integer, parameter, public :: OBC_STRAIN_SPECIFIED = 4
105!>@}
106integer, parameter :: NUM_PHYS_FIELDS = 13 !< Number of physical fields
107!>@{ Indices of physical field positions in segment%field array
108integer, parameter :: &
109 F_U = 1, F_V = 2, F_VX = 3, F_UY = 4, F_Z = 5, F_UAMP = 6, F_UPHASE = 7, &
110 F_VAMP = 8, F_VPHASE = 9, F_ZAMP = 10, F_ZPHASE = 11, F_T = 12, F_S = 13
111!>@}
112character(len=8), parameter :: PHYS_FIELD_NAMES(NUM_PHYS_FIELDS) = &
113 [character(len=8) :: 'U', 'V', 'DVDX', 'DUDY', 'SSH', 'Uamp', &
114 'Uphase', 'Vamp', 'Vphase', 'SSHamp', 'SSHphase', 'TEMP', 'SALT'] !< Physical field name
115 !! strings used by input parameter
116
117!> Open boundary segment data from files (mostly).
118type, public :: OBC_segment_data_type
119 type(external_field) :: handle !< handle from FMS associated with segment data on disk
120 type(external_field) :: dz_handle !< handle from FMS associated with segment thicknesses on disk
121 logical :: required = .false. !< True if this field is required
122 logical :: use_IO = .false. !< True if segment data is based on file input
123 character(len=32) :: name !< A name identifier for the segment data. When there is grid
124 !! rotation, this is the name on the rotated internal grid.
125 integer :: tr_index = -1 !< If this field is a tracer, its index in registry is stored here.
126 logical :: bgc_tracer !< True if this field is a BGC tracer
127 logical :: on_face !< If true, this field is discretized on the OBC segment
128 !! (velocity-point) faces, or if false it as the vorticiy points
129 real :: scale !< A scaling factor for converting input data to
130 !! the internal units of this field. For salinity this would
131 !! be in units of [S ppt-1 ~> 1]
132 real, allocatable :: buffer_src(:,:,:) !< buffer for segment data located at cell faces and on
133 !! the original vertical grid in the internally scaled
134 !! units for the field in question, such as [L T-1 ~> m s-1]
135 !! for a velocity or [S ~> ppt] for salinity.
136 integer :: nk_src !< Number of vertical levels in the source data
137 real, allocatable :: dz_src(:,:,:) !< vertical grid cell spacing of the incoming segment
138 !! data in [Z ~> m].
139 real, allocatable :: buffer_dst(:,:,:) !< buffer src data remapped to the target vertical grid
140 !! in the internally scaled units for the field in
141 !! question, such as [L T-1 ~> m s-1] for a velocity or
142 !! [S ~> ppt] for salinity.
143 real :: value !< A constant value for the inflow concentration if not read
144 !! from file, in the internal units of a field, such as [S ~> ppt]
145 !! for salinity.
146 real :: resrv_lfac_in = 1. !< The reservoir inverse length scale factor for the inward
147 !! direction per field [nondim]. The general 1/Lscale_in is
148 !! multiplied by this factor for a specific tracer or thickness.
149 real :: resrv_lfac_out= 1. !< The reservoir inverse length scale factor for the outward
150 !! direction per field [nondim]. The general 1/Lscale_out is
151 !! multiplied by this factor for a specific tracer or thickness.
152end type OBC_segment_data_type
153
154!> Tracer on OBC segment data structure, for putting into a segment tracer registry.
155type, public :: OBC_segment_tracer_type
156 real, allocatable :: t(:,:,:) !< tracer concentration array in rescaled units,
157 !! like [S ~> ppt] for salinity.
158 real :: OBC_inflow_conc = 0.0 !< tracer concentration for generic inflows in rescaled units,
159 !! like [S ~> ppt] for salinity.
160 character(len=32) :: name !< tracer name used for error messages
161 type(tracer_type), pointer :: Tr => NULL() !< metadata describing the tracer
162 real, allocatable :: tres(:,:,:) !< tracer reservoir array in rescaled units,
163 !! like [S ~> ppt] for salinity.
164 real :: scale !< A scaling factor for converting the units of input
165 !! data, like [S ppt-1 ~> 1] for salinity.
166 logical :: is_initialized !< reservoir values have been set when True
167 integer :: ntr_index = -1 !< index of segment tracer in the global tracer registry
168 integer :: fd_index = -1 !< index of segment tracer in the input fields
169end type OBC_segment_tracer_type
170
171!> Thickness on OBC segment data structure, with a reservoir
172type, public :: OBC_segment_thickness_type
173 real, allocatable :: h(:,:,:) !< layer thickness array in rescaled units, [Z ~> m].
174 real :: OBC_inflow_conc = 0.0 !< layer thickness for generic inflows in rescaled units,
175 !! [Z ~> m].
176 character(len=32) :: name !< thickness name used for error messages
177 real, allocatable :: h_res(:,:,:) !< thickness reservoir array in rescaled units,
178 !! [Z ~> m].
179 real :: scale !< A scaling factor for converting the units of input
180 !! data, [Z m-1 ~> 1].
181 logical :: is_initialized !< reservoir values have been set when True
182 integer :: fd_index = -1 !< index of segment thickness in the input fields
183end type OBC_segment_thickness_type
184
185!> Registry type for tracers on segments
186type, public :: segment_tracer_registry_type
187 integer :: ntseg = 0 !< number of registered tracer segments
188 type(OBC_segment_tracer_type) :: Tr(MAX_FIELDS_) !< array of registered tracers
189 logical :: locked = .false. !< New tracers may be registered if locked=.false.
190 !! When locked=.true.,no more tracers can be registered.
191 !! Not sure who should lock it or when...
192end type segment_tracer_registry_type
193
194!> Open boundary segment data structure. Unless otherwise noted, 2-d and 3-d arrays are discretized
195!! at the same position as normal velocity points in the middle of the OBC segments.
196type, public :: OBC_segment_type
197 logical :: Flather !< If true, applies Flather + Chapman radiation of barotropic gravity waves.
198 logical :: radiation !< If true, 1D Orlanksi radiation boundary conditions are applied.
199 !! If False, a gradient condition is applied.
200 logical :: radiation_tan !< If true, 1D Orlanksi radiation boundary conditions are applied to
201 !! tangential flows.
202 logical :: radiation_grad !< If true, 1D Orlanksi radiation boundary conditions are applied to
203 !! dudv and dvdx.
204 logical :: oblique !< Oblique waves supported at radiation boundary.
205 logical :: oblique_tan !< If true, 2D radiation boundary conditions are applied to
206 !! tangential flows.
207 logical :: oblique_grad !< If true, 2D radiation boundary conditions are applied to
208 !! dudv and dvdx.
209 logical :: nudged !< Optional supplement to radiation boundary.
210 logical :: nudged_tan !< Optional supplement to nudge tangential velocity.
211 logical :: nudged_grad !< Optional supplement to nudge normal gradient of tangential velocity.
212 logical :: specified !< Boundary normal velocity fixed to external value.
213 logical :: specified_tan !< Boundary tangential velocity fixed to external value.
214 logical :: specified_grad !< Boundary gradient of tangential velocity fixed to external value.
215 logical :: open !< Boundary is open for continuity solver, and there are no other
216 !! parameterized mass fluxes at the open boundary.
217 logical :: gradient !< Zero gradient at boundary.
218 integer :: direction !< Boundary faces one of the four directions.
219 logical :: is_N_or_S !< True if the OB is facing North or South and exists on this PE.
220 logical :: is_E_or_W !< True if the OB is facing East or West and exists on this PE.
221 logical :: is_E_or_W_2 !< True if the OB is facing East or West anywhere.
222 type(OBC_segment_data_type), pointer :: field(:) => NULL() !< OBC data
223 integer :: num_fields !< number of OBC data fields (e.g. u_normal,u_parallel and eta for Flather)
224 integer :: Is_obc !< Starting local i-index of boundary segment, this may be outside of the local PE.
225 integer :: Ie_obc !< Ending local i-index of boundary segment, this may be outside of the local PE.
226 integer :: Js_obc !< Starting local j-index of boundary segment, this may be outside of the local PE.
227 integer :: Je_obc !< Ending local j-index of boundary segment, this may be outside of the local PE.
228 real :: Velocity_nudging_timescale_in !< Nudging timescale on inflow [T ~> s].
229 real :: Velocity_nudging_timescale_out !< Nudging timescale on outflow [T ~> s].
230 logical :: on_pe !< true if any portion of the segment is located in this PE's data domain
231 logical :: temp_segment_data_exists !< true if temperature data arrays are present
232 logical :: salt_segment_data_exists !< true if salinity data arrays are present
233 real, allocatable :: Htot(:,:) !< The total column thickness [H ~> m or kg m-2] at OBC-points.
234 real, allocatable :: dZtot(:,:) !< The total column vertical extent [Z ~> m] at OBC segment faces.
235 real, allocatable :: normal_vel(:,:,:) !< The layer velocity normal to the OB
236 !! segment [L T-1 ~> m s-1].
237 real, allocatable :: tangential_vel(:,:,:) !< The layer velocity tangential to the OB segment
238 !! [L T-1 ~> m s-1], discretized at the corner points.
239 real, allocatable :: tangential_grad(:,:,:) !< The gradient of the velocity tangential to the OB
240 !! segment [T-1 ~> s-1], discretized at the corner points.
241 real, allocatable :: normal_trans(:,:,:) !< The layer transport normal to the OB
242 !! segment [H L2 T-1 ~> m3 s-1].
243 real, allocatable :: normal_vel_bt(:,:) !< The barotropic velocity normal to
244 !! the OB segment [L T-1 ~> m s-1].
245 real, allocatable :: normal_trans_bt(:,:) !< The barotropic transport normal
246 !! the OB segment [H L2 T-1 ~> m3 s-1 or kg s-1].
247 real, allocatable :: tidal_vn(:,:) !< The barotropic tidal velocity normal to
248 !! the OB segment [L T-1 ~> m s-1].
249 real, allocatable :: tidal_vt(:,:) !< The barotropic tidal velocity tangential to
250 !! the OB segment [L T-1 ~> m s-1].
251 real, allocatable :: SSH(:,:) !< The sea-surface elevation along the
252 !! segment [Z ~> m].
253 real, allocatable :: tidal_elev(:,:) !< Tidal elevation at the OBC points [Z ~> m]
254 real, allocatable :: grad_normal(:,:,:) !< The gradient of the normal flow along the
255 !! segment times the grid spacing [L T-1 ~> m s-1],
256 !! with the first index being the corner-point index
257 !! along the segment, and the second index being 1 (for
258 !! values one point into the domain) or 2 (for values
259 !! along the OBC itself)
260 real, allocatable :: grad_tan(:,:,:) !< The gradient of the tangential flow along the
261 !! segment times the grid spacing [L T-1 ~> m s-1], with the
262 !! first index being the velocity/tracer point index along the
263 !! segment, and the second being 1 for the value 1.5 points
264 !! inside the domain and 2 for the value half a point
265 !! inside the domain.
266 real, allocatable :: grad_gradient(:,:,:) !< The gradient normal to the segment of the gradient
267 !! tangetial to the segment of tangential flow along the segment
268 !! times the grid spacing [T-1 ~> s-1], with the first
269 !! index being the velocity/tracer point index along the segment,
270 !! and the second being 1 for the value 2 points into the domain
271 !! and 2 for the value 1 point into the domain.
272 real, allocatable :: rx_norm_rad(:,:,:) !< The previous normal phase speed use for EW radiation
273 !! OBC, in grid points per timestep [nondim]
274 real, allocatable :: ry_norm_rad(:,:,:) !< The previous normal phase speed use for NS radiation
275 !! OBC, in grid points per timestep [nondim]
276 real, allocatable :: rx_norm_obl(:,:,:) !< The previous x-direction normalized radiation coefficient
277 !! for either EW or NS oblique OBCs [L2 T-2 ~> m2 s-2]
278 real, allocatable :: ry_norm_obl(:,:,:) !< The previous y-direction normalized radiation coefficient
279 !! for either EW or NS oblique OBCs [L2 T-2 ~> m2 s-2]
280 real, allocatable :: cff_normal(:,:,:) !< The denominator for oblique radiation of the normal
281 !! velocity [L2 T-2 ~> m2 s-2]
282 real, allocatable :: nudged_normal_vel(:,:,:) !< The layer velocity normal to the OB segment
283 !! that values should be nudged towards [L T-1 ~> m s-1].
284 real, allocatable :: nudged_tangential_vel(:,:,:) !< The layer velocity tangential to the OB segment
285 !! that values should be nudged towards [L T-1 ~> m s-1],
286 !! discretized at the corner (PV) points.
287 real, allocatable :: nudged_tangential_grad(:,:,:) !< The layer dvdx or dudy towards which nudging
288 !! can occur [T-1 ~> s-1].
289 type(OBC_segment_thickness_type), pointer :: h_Reg=> NULL()!< A pointer to the thickness for the segment.
290 type(segment_tracer_registry_type), pointer :: tr_Reg=> NULL()!< A pointer to the tracer registry for the segment.
291 type(hor_index_type) :: HI !< Horizontal index ranges
292 real :: Tr_InvLscale_out !< An effective inverse length scale for restoring
293 !! the tracer concentration in a fictitious
294 !! reservoir towards interior values when flow
295 !! is exiting the domain [L-1 ~> m-1]
296 real :: Tr_InvLscale_in !< An effective inverse length scale for restoring
297 !! the tracer concentration towards an externally
298 !! imposed value when flow is entering [L-1 ~> m-1]
299 real :: Th_InvLscale_out !< An effective inverse length scale for restoring
300 !! the layer thickness in a fictitious
301 !! reservoir towards interior values when flow
302 !! is exiting the domain [L-1 ~> m-1]
303 real :: Th_InvLscale_in !< An effective inverse length scale for restoring
304 !! the layer thickness towards an externally
305 !! imposed value when flow is entering [L-1 ~> m-1]
306end type OBC_segment_type
307
308!> Open-boundary data
309type, public :: ocean_OBC_type
310 integer :: number_of_segments = 0 !< The number of open-boundary segments.
311 logical :: reverse_segment_order = .false. !< If true, store the segments internally in the reversed order.
312 integer :: ke = 0 !< The number of model layers
313 logical :: open_u_BCs_exist_globally = .false. !< True if any zonal velocity points
314 !! in the global domain use open BCs.
315 logical :: open_v_BCs_exist_globally = .false. !< True if any meridional velocity points
316 !! in the global domain use open BCs.
317 logical :: Flather_u_BCs_exist_globally = .false. !< True if any zonal velocity points
318 !! in the global domain use Flather BCs.
319 logical :: Flather_v_BCs_exist_globally = .false. !< True if any meridional velocity points
320 !! in the global domain use Flather BCs.
321 logical :: oblique_BCs_exist_globally = .false. !< True if any velocity points
322 !! in the global domain use oblique BCs.
323 logical :: nudged_u_BCs_exist_globally = .false. !< True if any velocity points in the
324 !! global domain use nudged BCs.
325 logical :: nudged_v_BCs_exist_globally = .false. !< True if any velocity points in the
326 !! global domain use nudged BCs.
327 logical :: specified_u_BCs_exist_globally = .false. !< True if any zonal velocity points
328 !! in the global domain use specified BCs.
329 logical :: specified_v_BCs_exist_globally = .false. !< True if any meridional velocity points
330 !! in the global domain use specified BCs.
331 logical :: radiation_BCs_exist_globally = .false. !< True if radiations BCs are in use anywhere.
332 logical :: user_BCs_set_globally = .false. !< True if any OBC_USER_CONFIG is set
333 !! for input from user directory.
334 logical :: update_OBC = .false. !< Is OBC data time-dependent
335 logical :: update_OBC_seg_data = .false. !< Is it the time for OBC segment data update for fields that
336 !! require less frequent update
337 logical :: any_needs_IO_for_data = .false. !< Is any i/o needed for OBCs globally
338 integer :: vorticity_config !< An integer indicating OBC relative vorticity configuration
339 integer :: strain_config !< An integer indicating OBC strain configuration
340 logical :: zero_biharmonic = .false. !< If True, zeros the Laplacian of flow on open boundaries for
341 !! use in the biharmonic viscosity term.
342 logical :: brushcutter_mode = .false. !< If True, read data on supergrid.
343 logical, allocatable :: tracer_x_reservoirs_used(:) !< Dimensioned by the number of tracers, set globally,
344 !! true for those with x reservoirs (needed for restarts).
345 logical, allocatable :: tracer_y_reservoirs_used(:) !< Dimensioned by the number of tracers, set globally,
346 !! true for those with y reservoirs (needed for restarts).
347 logical :: thickness_x_reservoirs_used = .false. !< True for thichness reservoirs in x (needed for restarts).
348 logical :: thickness_y_reservoirs_used = .false. !< True for thichness reservoirs in y (needed for restarts).
349 integer :: ntr = 0 !< number of tracers
350 integer :: n_tide_constituents = 0 !< Number of tidal constituents to add to the boundary.
351 logical :: add_tide_constituents = .false. !< If true, add tidal constituents to the boundary elevation
352 !! and velocity. Will be set to true if n_tide_constituents > 0.
353 character(len=2), allocatable, dimension(:) :: tide_names !< Names of tidal constituents to add to the boundary data.
354 real, allocatable, dimension(:) :: tide_frequencies !< Angular frequencies of chosen tidal
355 !! constituents [rad T-1 ~> rad s-1].
356 real, allocatable, dimension(:) :: tide_eq_phases !< Equilibrium phases of chosen tidal constituents [rad].
357 real, allocatable, dimension(:) :: tide_fn !< Amplitude modulation of boundary tides by nodal cycle [nondim].
358 real, allocatable, dimension(:) :: tide_un !< Phase modulation of boundary tides by nodal cycle [rad].
359 logical :: add_eq_phase = .false. !< If true, add the equilibrium phase argument
360 !! to the specified boundary tidal phase.
361 logical :: add_nodal_terms = .false. !< If true, insert terms for the 18.6 year modulation when
362 !! calculating tidal boundary conditions.
363 type(time_type) :: time_ref !< Reference date (t = 0) for tidal forcing.
364 type(astro_longitudes) :: tidal_longitudes !< Lunar and solar longitudes used to calculate tidal forcing.
365 ! Properties of the segments used.
366 type(OBC_segment_type), allocatable :: segment(:) !< List of segment objects.
367 ! Which segment object describes the current point.
368 integer, allocatable :: segnum_u(:,:) !< The absolute value gives the segment number of any OBCs at u-points,
369 !! while the sign indicates whether they are Eastern (> 0) or Western (< 0)
370 !! OBCs, with 0 for velocities that are not on an OBC.
371 integer, allocatable :: segnum_v(:,:) !< The absolute value gives the segment number of any OBCs at v-points,
372 !! while the sign indicates whether they are Northern (> 0) or Southern (< 0)
373 !! OBCs, with 0 for velocities that are not on an OBC.
374 ! Keep the OBC segment properties for external BGC tracers
375 type(external_tracers_segments_props), pointer :: obgc_segments_props => NULL() !< obgc segment properties
376 integer :: num_obgc_tracers = 0 !< The total number of obgc tracers
377
378 ! The following parameters are used in the baroclinic radiation code:
379 real :: gamma_uv !< The relative weighting for the baroclinic radiation
380 !! velocities (or speed of characteristics) at the
381 !! new time level (1) or the running mean (0) for velocities [nondim].
382 !! Valid values range from 0 to 1, with a default of 0.3.
383 real :: rx_max !< The maximum magnitude of the baroclinic radiation velocity (or speed of
384 !! characteristics) in units of grid points per timestep [nondim].
385 logical :: OBC_pe !< Is there an open boundary on this tile?
386 logical :: u_OBCs_on_PE !< True if there are any u-point OBCs on this PE, including in its halos.
387 logical :: v_OBCs_on_PE !< True if there are any v-point OBCs on this PE, including in its halos.
388 logical :: v_N_OBCs_on_PE !< True if there are any northern v-point OBCs on this PE, including in its halos.
389 logical :: v_S_OBCs_on_PE !< True if there are any southern v-point OBCs on this PE, including in its halos.
390 logical :: u_E_OBCs_on_PE !< True if there are any eastern u-point OBCs on this PE, including in its halos.
391 logical :: u_W_OBCs_on_PE !< True if there are any western u-point OBCs on this PE, including in its halos.
392 !>@{ Index ranges on the local PE for the open boundary conditions in various directions
393 integer :: Is_u_W_obc, Ie_u_W_obc, js_u_W_obc, je_u_W_obc
394 integer :: Is_u_E_obc, Ie_u_E_obc, js_u_E_obc, je_u_E_obc
395 integer :: is_v_S_obc, ie_v_S_obc, Js_v_S_obc, Je_v_S_obc
396 integer :: is_v_N_obc, ie_v_N_obc, Js_v_N_obc, Je_v_N_obc
397 !>@}
398 type(remapping_CS), pointer :: remap_z_CS => NULL() !< ALE remapping control structure for
399 !! z-space data on segments
400 type(remapping_CS), pointer :: remap_h_CS => NULL() !< ALE remapping control structure for
401 !! thickness-based fields on segments
402 type(OBC_registry_type), pointer :: OBC_Reg => NULL() !< Registry type for boundaries
403 real, allocatable :: rx_normal(:,:,:) !< Array storage for normal phase speed for EW radiation OBCs
404 !! in units of grid points per timestep [nondim]
405 real, allocatable :: ry_normal(:,:,:) !< Array storage for normal phase speed for NS radiation OBCs
406 !! in units of grid points per timestep [nondim]
407 real, allocatable :: rx_oblique_u(:,:,:) !< X-direction oblique boundary condition radiation speeds
408 !! squared at u points for restarts [L2 T-2 ~> m2 s-2]
409 real, allocatable :: ry_oblique_u(:,:,:) !< Y-direction oblique boundary condition radiation speeds
410 !! squared at u points for restarts [L2 T-2 ~> m2 s-2]
411 real, allocatable :: rx_oblique_v(:,:,:) !< X-direction oblique boundary condition radiation speeds
412 !! squared at v points for restarts [L2 T-2 ~> m2 s-2]
413 real, allocatable :: ry_oblique_v(:,:,:) !< Y-direction oblique boundary condition radiation speeds
414 !! squared at v points for restarts [L2 T-2 ~> m2 s-2]
415 real, allocatable :: cff_normal_u(:,:,:) !< Denominator for normalizing EW oblique boundary condition
416 !! radiation rates at u points for restarts [L2 T-2 ~> m2 s-2]
417 real, allocatable :: cff_normal_v(:,:,:) !< Denominator for normalizing NS oblique boundary condition
418 !! radiation rates at v points for restarts [L2 T-2 ~> m2 s-2]
419 real, allocatable :: tres_x(:,:,:,:) !< Array storage of tracer reservoirs for restarts,
420 !! in unscaled units [conc]
421 real, allocatable :: tres_y(:,:,:,:) !< Array storage of tracer reservoirs for restarts,
422 !! in unscaled units [conc]
423 real, allocatable :: h_res_x(:,:,:) !< Array storage of thickness reservoirs for restarts,
424 !! [Z ~> m]
425 real, allocatable :: h_res_y(:,:,:) !< Array storage of thickness reservoirs for restarts,
426 !! [Z ~> m]
427 logical :: use_h_res = .false. !< If true, use thickness reservoirs
428 logical :: debug !< If true, write verbose checksums for debugging purposes.
429 integer :: nk_OBC_debug = 0 !< The number of layers of OBC segment data to write out
430 !! in full when DEBUG_OBCS is true.
431 real :: silly_h !< A silly value of thickness outside of the domain that can be used to test
432 !! the independence of the OBCs to this external data [Z ~> m].
433 real :: silly_u !< A silly value of velocity outside of the domain that can be used to test
434 !! the independence of the OBCs to this external data [L T-1 ~> m s-1].
435 logical :: ramp = .false. !< If True, ramp from zero to the external values for SSH.
436 logical :: ramping_is_activated = .false. !< True if the ramping has been initialized
437 real :: ramp_timescale !< If ramp is True, use this timescale for ramping [T ~> s].
438 real :: trunc_ramp_time !< If ramp is True, time after which ramp is done [T ~> s].
439 real :: ramp_value !< If ramp is True, where we are on the ramp from
440 !! zero to one [nondim].
441 type(time_type) :: ramp_start_time !< Time when model was started.
442 integer :: remap_answer_date !< The vintage of the order of arithmetic and expressions to use
443 !! for remapping. Values below 20190101 recover the remapping
444 !! answers from 2018, while higher values use more robust
445 !! forms of the same remapping expressions.
446 logical :: check_reconstruction !< Flag for remapping to run checks on reconstruction
447 logical :: check_remapping !< Flag for remapping to run internal checks
448 logical :: force_bounds_in_subcell !< Flag for remapping to hide overshoot using bounds
449 logical :: om4_remap_via_sub_cells !< If true, use the OM4 remapping algorithm
450 character(40) :: remappingScheme !< String selecting the vertical remapping scheme
451 type(group_pass_type) :: pass_oblique !< Structure for group halo pass
452 logical :: exterior_OBC_bug !< If true, use incorrect form of tracers exterior to OBCs.
453 logical :: hor_index_bug !< If true, recover set of a horizontal indexing bugs in the OBC code.
454 logical :: reservoir_init_bug !< If true, set the OBC tracer reservoirs at the startup of a new
455 !! run from the interior tracer concentrations regardless of
456 !! properties that may be explicitly specified for the reservoir
457 !! concentrations.
458 logical :: ts_needed_bug !< If true, recover a bug that temperature and salinity can be ignored
459 !! even if they are registered tracers in the rest of the model.
460end type ocean_OBC_type
461
462!> Control structure for open boundaries that read from files.
463!! Probably lots to update here.
464type, public :: file_OBC_CS ; private
465 logical :: OBC_file_used = .false. !< Placeholder for now to avoid an empty type.
466end type file_OBC_CS
467
468!> Type to carry something (what??) for the OBC registry.
469type, public :: OBC_struct_type
470 character(len=32) :: name !< OBC name used for error messages
471end type OBC_struct_type
472
473!> Type to carry basic OBC information needed for updating values.
474type, public :: OBC_registry_type
475 integer :: nobc = 0 !< number of registered open boundary types.
476 type(OBC_struct_type) :: OB(MAX_FIELDS_) !< array of registered boundary types.
477 logical :: locked = .false. !< New OBC types may be registered if locked=.false.
478 !! When locked=.true.,no more boundaries can be registered.
479end type OBC_registry_type
480
481!> Type to carry OBC information needed for setting segments for OBGC tracers
482type, private :: external_tracers_segments_props
483 type(external_tracers_segments_props), pointer :: next => NULL() !< pointer to the next node
484 character(len=128) :: tracer_name !< tracer name
485 character(len=128) :: tracer_src_file !< tracer source file for BC
486 character(len=128) :: tracer_src_field !< name of the field in source file to extract BC
487 real :: lfac_in !< multiplicative factor for inbound tracer reservoir length scale [nondim]
488 real :: lfac_out !< multiplicative factor for outbound tracer reservoir length scale [nondim]
489end type external_tracers_segments_props
490integer :: id_clock_pass !< A CPU time clock
491
492character(len=40) :: mdl = "MOM_open_boundary" !< This module's name.
493
494contains
495
496!> Enables OBC module and reads configuration parameters
497!! This routine is called from MOM_initialize_fixed which
498!! occurs before the initialization of the vertical coordinate
499!! and ALE_init. Therefore segment data are not fully initialized
500!! here. The remainder of the segment data are initialized in a
501!! later call to update_open_boundary_data
5021subroutine open_boundary_config(G, US, param_file, OBC)
503 type(dyn_horgrid_type), intent(inout) :: G !< Ocean grid structure
504 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
505 type(param_file_type), intent(in) :: param_file !< Parameter file handle
506 type(ocean_OBC_type), pointer :: OBC !< Open boundary control structure
507
508 ! Local variables
509 integer :: num_of_segs ! Number of open boundary segments
510 integer :: n, n_seg ! For looping over segments
511 logical :: debug, mask_outside, reentrant_x, reentrant_y
512 character(len=15) :: segment_param_str ! The run-time parameter name for each segment
513 character(len=1024) :: segment_str ! The contents (rhs) for parameter "segment_param_str"
514 character(len=200) :: config ! A string to temporarily store a few runtime parameters
515 real :: Lscale_in, Lscale_out ! parameters controlling tracer values at the boundaries [L ~> m]
516 integer :: default_answer_date ! The default setting for the various ANSWER_DATE flags.
517 logical :: enable_bugs ! If true, the defaults for recently added bug-fix flags are set to
518 ! recreate the bugs, or if false bugs are only used if actively selected.
519 logical :: debugging_tests ! If true, do additional calls resetting values to help debug the performance
520 ! of the open boundary condition code.
521 logical :: obsolete_param_set, param_set
522 logical :: zero_vorticity, freeslip_vorticity, computed_vorticity, specified_vorticity
523 logical :: zero_strain, freeslip_strain, computed_strain, specified_strain
524 ! This include declares and sets the variable "version".
525# include "version_variable.h"
526
527 call log_version(param_file, mdl, version, "Controls where open boundaries are located, "//&
528 "what kind of boundary condition to impose, and what data to apply, if any.", &
5291 all_default=.false.)
530 ! Parameter OBC_NUMBER_OF_SEGMENTS is always logged.
531 call get_param(param_file, mdl, "OBC_NUMBER_OF_SEGMENTS", num_of_segs, &
5321 "The number of open boundary segments.", default=0)
5331 if (num_of_segs <= 0) & ! Do nothing if there is no OBC segments
5341 return
535
5360 allocate(OBC)
5370 OBC%number_of_segments = num_of_segs
538 call get_param(param_file, mdl, "OBC_USER_CONFIG", config, &
539 "A string that sets how the open boundary conditions are "//&
5400 " configured: \n", default="none", do_not_log=.true.)
541 call get_param(param_file, mdl, "NK", OBC%ke, &
5420 "The number of model layers", default=0, do_not_log=.true.)
543
5440 if (config /= "none" .and. config /= "dyed_obcs") OBC%user_BCs_set_globally = .true.
545
546 ! Configuration for OBC relative vorticity.
547 ! Old setup method
5480 obsolete_param_set = .false.
5490 zero_vorticity = .false.
5500 call read_param(param_file, "OBC_ZERO_VORTICITY", zero_vorticity, set=param_set)
5510 obsolete_param_set = obsolete_param_set .or. param_set
5520 freeslip_vorticity = .true.
5530 call read_param(param_file, "OBC_FREESLIP_VORTICITY", freeslip_vorticity, set=param_set)
5540 obsolete_param_set = obsolete_param_set .or. param_set
5550 computed_vorticity = .false.
5560 call read_param(param_file, "OBC_COMPUTED_VORTICITY", computed_vorticity, set=param_set)
5570 obsolete_param_set = obsolete_param_set .or. param_set
5580 specified_vorticity = .false.
5590 call read_param(param_file, "OBC_SPECIFIED_VORTICITY", specified_vorticity, set=param_set)
5600 obsolete_param_set = obsolete_param_set .or. param_set
5610 if (obsolete_param_set) then
562 call MOM_error(WARNING, 'OBC_ZERO_VORTICITY, OBC_FREESLIP_VORTICITY, OBC_COMPUTED_VORTICITY'//&
5630 ' and OBC_SPECIFIED_VORTICITY are obsolete, use OBC_VORTICITY_CONFIG instead.')
564 if ((zero_vorticity .and. freeslip_vorticity) .or. &
565 (zero_vorticity .and. computed_vorticity) .or. &
566 (zero_vorticity .and. specified_vorticity) .or. &
567 (freeslip_vorticity .and. computed_vorticity) .or. &
5680 (freeslip_vorticity .and. specified_vorticity) .or. &
569 (computed_vorticity .and. specified_vorticity)) &
570 call MOM_error(FATAL, "MOM_open_boundary.F90, open_boundary_config:\n"//&
571 "Only one of OBC_ZERO_VORTICITY, OBC_FREESLIP_VORTICITY, OBC_COMPUTED_VORTICITY\n"//&
5720 "and OBC_IMPORTED_VORTICITY can be True at once.")
573 ! "config" is set from OBC_XXX_VORTICITY if they are used.
5740 if (zero_vorticity) then
5750 config = 'zero'
5760 elseif (freeslip_vorticity) then
5770 config = 'freeslip'
5780 elseif (computed_vorticity) then
5790 config = 'computed'
5800 elseif (specified_vorticity) then
5810 config = 'specified'
582 else
5830 config = 'none'
584 endif
585 else
5860 config = 'freeslip' ! Default
587 endif
588 ! New setup method (overrides old method if specified)
5890 call read_param(param_file, "OBC_VORTICITY_CONFIG", config)
590 call get_param(param_file, mdl, "OBC_VORTICITY_CONFIG", config, &
591 "Configuration for relative vorticity in momentum advection at open "//&
592 "boundaries. Options are: \n"// &
593 " \t none - No adjustment.\n"//&
594 " \t zero - Sets relative vorticity to zero.\n"//&
595 " \t freeslip - Sets the normal gradient of tangential velocity to zero.\n"//&
596 " \t computed - Computes the normal gradient of tangential velocity using\n"//&
597 " \t external values of tangential velocity.\n"//&
598 " \t specified - Uses the external values of the normal gradient of\n"//&
5990 " \t tangential velocity.", default="freeslip", do_not_read=.true.)
6000 select case (trim(config))
6010 case ("none") ; OBC%vorticity_config = OBC_VORTICITY_NONE
6020 case ("zero") ; OBC%vorticity_config = OBC_VORTICITY_ZERO
6030 case ("freeslip") ; OBC%vorticity_config = OBC_VORTICITY_FREESLIP
6040 case ("computed") ; OBC%vorticity_config = OBC_VORTICITY_COMPUTED
6050 case ("specified") ; OBC%vorticity_config = OBC_VORTICITY_SPECIFIED
606 case default
6070 call MOM_error(FATAL, "MOM_open_boundary: Unrecognized OBC_VORTICITY_CONFIG: "//trim(config))
608 end select
609
610 ! Configuration for OBC strain.
611 ! Old setup method
6120 obsolete_param_set = .false.
6130 zero_strain = .false.
6140 call read_param(param_file, "OBC_ZERO_STRAIN", zero_strain, set=param_set)
6150 obsolete_param_set = obsolete_param_set .or. param_set
6160 freeslip_strain = .true.
6170 call read_param(param_file, "OBC_FREESLIP_STRAIN", freeslip_strain, set=param_set)
6180 obsolete_param_set = obsolete_param_set .or. param_set
6190 computed_strain = .false.
6200 call read_param(param_file, "OBC_COMPUTED_STRAIN", computed_strain, set=param_set)
6210 obsolete_param_set = obsolete_param_set .or. param_set
6220 specified_strain = .false.
6230 call read_param(param_file, "OBC_SPECIFIED_STRAIN", specified_strain, set=param_set)
6240 obsolete_param_set = obsolete_param_set .or. param_set
6250 if (obsolete_param_set) then
626 call MOM_error(WARNING, 'OBC_ZERO_STRAIN, OBC_FREESLIP_STRAIN, OBC_COMPUTED_STRAIN'//&
6270 ' and OBC_SPECIFIED_STRAIN are obsolete, use OBC_STRAIN_CONFIG instead.')
628 if ((zero_strain .and. freeslip_strain) .or. &
629 (zero_strain .and. computed_strain) .or. &
630 (zero_strain .and. specified_strain) .or. &
631 (freeslip_strain .and. computed_strain) .or. &
6320 (freeslip_strain .and. specified_strain) .or. &
633 (computed_strain .and. specified_strain)) &
634 call MOM_error(FATAL, "MOM_open_boundary.F90, open_boundary_config: \n"//&
635 "Only one of OBC_ZERO_STRAIN, OBC_FREESLIP_STRAIN, OBC_COMPUTED_STRAIN \n"//&
6360 "and OBC_IMPORTED_STRAIN can be True at once.")
637 ! "config" is set from OBC_XXX_STRAIN if they are used.
6380 if (zero_strain) then
6390 config = 'zero'
6400 elseif (freeslip_strain) then
6410 config = 'freeslip'
6420 elseif (computed_strain) then
6430 config = 'computed'
6440 elseif (specified_strain) then
6450 config = 'specified'
646 else
6470 config = 'none'
648 endif
649 else
6500 config = 'freeslip' ! Default
651 endif
652 ! New setup method (overrides old method if specified)
6530 call read_param(param_file, "OBC_STRAIN_CONFIG", config)
654 call get_param(param_file, mdl, "OBC_STRAIN_CONFIG", config, &
655 "Configuration for strain in horizontal viscosity at open boundaries. "//&
656 "Options are: \n"// &
657 " \t none - No adjustment.\n"//&
658 " \t zero - Sets strain to zero.\n"//&
659 " \t freeslip - Sets the normal gradient of tangential velocity to zero.\n"//&
660 " \t computed - Computes the normal gradient of tangential velocity using\n"//&
661 " \t external values of tangential velocity.\n"//&
662 " \t specified - Uses the external values of the normal gradient of\n"//&
6630 " \t tangential velocity.", default="freeslip", do_not_read=.true.)
6640 select case (trim(config))
6650 case ("none") ; OBC%strain_config = OBC_STRAIN_NONE
6660 case ("zero") ; OBC%strain_config = OBC_STRAIN_ZERO
6670 case ("freeslip") ; OBC%strain_config = OBC_STRAIN_FREESLIP
6680 case ("computed") ; OBC%strain_config = OBC_STRAIN_COMPUTED
6690 case ("specified") ; OBC%strain_config = OBC_STRAIN_SPECIFIED
670 case default
6710 call MOM_error(FATAL, "MOM_open_boundary: Unrecognized OBC_STRAIN_CONFIG: "//trim(config))
672 end select
673
674 call get_param(param_file, mdl, "OBC_ZERO_BIHARMONIC", OBC%zero_biharmonic, &
675 "If true, zeros the Laplacian of flow on open boundaries in the biharmonic "//&
6760 "viscosity term.", default=.false.)
677 call get_param(param_file, mdl, "MASK_OUTSIDE_OBCS", mask_outside, &
678 "If true, set the areas outside open boundaries to be land.", &
6790 default=.false.)
680 call get_param(param_file, mdl, "RAMP_OBCS", OBC%ramp, &
681 "If true, ramps from zero to the external values over time, with "//&
682 "a ramping timescale given by RAMP_TIMESCALE. Ramping SSH only so far.", &
6830 default=.false.)
684 call get_param(param_file, mdl, "OBC_RAMP_TIMESCALE", OBC%ramp_timescale, &
685 "If RAMP_OBCS is true, this sets the ramping timescale.", &
6860 units="days", default=1.0, scale=86400.0*US%s_to_T)
687 call get_param(param_file, mdl, "OBC_TIDE_N_CONSTITUENTS", OBC%n_tide_constituents, &
688 "Number of tidal constituents being added to the open boundary.", &
6890 default=0)
6900 OBC%add_tide_constituents = (OBC%n_tide_constituents > 0)
691
6920 call get_param(param_file, mdl, "DEBUG", debug, default=.false.)
693 call get_param(param_file, mdl, "DEBUG_OBCS", OBC%debug, &
694 "If true, do additional calls to help debug the performance "//&
695 "of the open boundary condition code.", &
6960 default=.false., debuggingParam=.true.)
6970 if (OBC%debug .and. (num_PEs() > 1)) &
6980 call MOM_error(FATAL, "DEBUG_OBCS = True is currently only supported for single PE runs.")
699 call get_param(param_file, mdl, "OBC_DEBUGGING_TESTS", debugging_tests, &
700 "If true, do additional calls resetting certain values to help verify the correctness "//&
701 "of the open boundary condition code.", &
7020 default=.false., old_name="DEBUG_OBC", debuggingParam=.true.)
703 call get_param(param_file, mdl, "NK_OBC_DEBUG", OBC%nk_OBC_debug, &
704 "The number of layers of OBC segment data to write out in full "//&
705 "when DEBUG_OBCS is true.", &
7060 default=0, debuggingParam=.true., do_not_log=.not.OBC%debug)
707 call get_param(param_file, mdl, "OBC_REVERSE_SEGMENT_ORDER", OBC%reverse_segment_order, &
708 "If true, store the OBC segments internally and handle them in the reverse "//&
709 "order from that with which they are specified via external parameters to test "//&
710 "for dependencies on the order with which the OBC segments are applied.", &
7110 default=.false., debuggingParam=.true., do_not_log=(OBC%number_of_segments<2))
712
713 call get_param(param_file, mdl, "OBC_SILLY_THICK", OBC%silly_h, &
714 "A silly value of thicknesses used outside of open boundary "//&
715 "conditions for debugging.", units="m", default=0.0, scale=US%m_to_Z, &
7160 do_not_log=.not.debugging_tests, debuggingParam=.true.)
717 call get_param(param_file, mdl, "OBC_SILLY_VEL", OBC%silly_u, &
718 "A silly value of velocities used outside of open boundary "//&
719 "conditions for debugging.", units="m/s", default=0.0, scale=US%m_s_to_L_T, &
7200 do_not_log=.not.debugging_tests, debuggingParam=.true.)
721 call get_param(param_file, mdl, "ENABLE_BUGS_BY_DEFAULT", enable_bugs, &
7220 default=.true., do_not_log=.true.) ! This is logged from MOM.F90.
723 call get_param(param_file, mdl, "EXTERIOR_OBC_BUG", OBC%exterior_OBC_bug, &
724 "If true, recover a bug in barotropic solver and other routines when "//&
725 "boundary contitions interior to the domain are used.", &
7260 default=enable_bugs)
727 call get_param(param_file, mdl, "OBC_HOR_INDEXING_BUG", OBC%hor_index_bug, &
728 "If true, recover set of a horizontal indexing bugs in the OBC code.", &
7290 default=enable_bugs)
730 call get_param(param_file, mdl, "OBC_RESERVOIR_INIT_BUG", OBC%reservoir_init_bug, &
731 "If true, set the OBC tracer reservoirs at the startup of a new run from the "//&
732 "interior tracer concentrations regardless of properties that may be explicitly "//&
7330 "specified for the reservoir concentrations.", default=enable_bugs, do_not_log=.true.)
734 call get_param(param_file, mdl, "OBC_TEMP_SALT_NEEDED_BUG", OBC%ts_needed_bug, &
735 "If true, recover a bug that OBC temperature and salinity can be ignored "//&
7360 "even if they are registered tracers in the rest of the model.", default=.true.)
7370 call get_param(param_file, mdl, "REENTRANT_X", reentrant_x, default=.true.)
7380 call get_param(param_file, mdl, "REENTRANT_Y", reentrant_y, default=.false.)
739
740 ! Allocate everything
7410 allocate(OBC%segment(1:OBC%number_of_segments))
7420 do n=1,OBC%number_of_segments
7430 OBC%segment(n)%Flather = .false.
7440 OBC%segment(n)%radiation = .false.
7450 OBC%segment(n)%radiation_tan = .false.
7460 OBC%segment(n)%radiation_grad = .false.
7470 OBC%segment(n)%oblique = .false.
7480 OBC%segment(n)%oblique_tan = .false.
7490 OBC%segment(n)%oblique_grad = .false.
7500 OBC%segment(n)%nudged = .false.
7510 OBC%segment(n)%nudged_tan = .false.
7520 OBC%segment(n)%nudged_grad = .false.
7530 OBC%segment(n)%specified = .false.
7540 OBC%segment(n)%specified_tan = .false.
7550 OBC%segment(n)%specified_grad = .false.
7560 OBC%segment(n)%open = .false.
7570 OBC%segment(n)%gradient = .false.
7580 OBC%segment(n)%direction = OBC_NONE
7590 OBC%segment(n)%is_N_or_S = .false.
7600 OBC%segment(n)%is_E_or_W = .false.
7610 OBC%segment(n)%is_E_or_W_2 = .false.
7620 OBC%segment(n)%Velocity_nudging_timescale_in = 0.0
7630 OBC%segment(n)%Velocity_nudging_timescale_out = 0.0
7640 OBC%segment(n)%num_fields = 0
765 enddo
7660 allocate(OBC%segnum_u(G%IsdB:G%IedB,G%jsd:G%jed), source=0)
7670 allocate(OBC%segnum_v(G%isd:G%ied,G%JsdB:G%JedB), source=0)
7680 OBC%u_OBCs_on_PE = .false.
7690 OBC%v_OBCs_on_PE = .false.
770
7710 do n=1,OBC%number_of_segments
7720 n_seg = n ; if (OBC%reverse_segment_order) n_seg = OBC%number_of_segments + 1 - n
7730 write(segment_param_str(1:15),"('OBC_SEGMENT_',i3.3)") n
774 call get_param(param_file, mdl, segment_param_str, segment_str, &
775 "Documentation needs to be dynamic?????", &
7760 fail_if_missing=.true.)
7770 segment_str = remove_spaces(segment_str)
7780 if (segment_str(1:2) == 'I=') then
7790 call setup_u_point_obc(OBC, G, US, segment_str, n_seg, n, param_file, reentrant_y)
7800 elseif (segment_str(1:2) == 'J=') then
7810 call setup_v_point_obc(OBC, G, US, segment_str, n_seg, n, param_file, reentrant_x)
782 else
783 call MOM_error(FATAL, "MOM_open_boundary.F90, open_boundary_config: "//&
7840 "Unable to interpret "//segment_param_str//" = "//trim(segment_str))
785 endif
786 enddo
787 ! Set arrays indicating the segment number and segment direction, and also store the
788 ! range of indices within which various orientations of OBCs can be found on this PE.
7890 call set_segnum_signs(OBC, G)
790
791 ! Moved this earlier because time_interp_external_init needs to be called
792 ! before anything that uses time_interp_external (such as initialize_segment_data)
793 if (OBC%specified_u_BCs_exist_globally .or. OBC%specified_v_BCs_exist_globally .or. &
7940 OBC%open_u_BCs_exist_globally .or. OBC%open_v_BCs_exist_globally) then
795 ! Need this for ocean_only mode boundary interpolation.
7960 call time_interp_external_init()
797 endif
798 ! if (open_boundary_query(OBC, needs_ext_seg_data=.true.)) &
799 ! call initialize_segment_data(G, OBC, param_file)
800
8010 if (open_boundary_query(OBC, apply_open_OBC=.true.)) then
802 call get_param(param_file, mdl, "OBC_RADIATION_MAX", OBC%rx_max, &
803 "The maximum magnitude of the baroclinic radiation velocity (or speed of "//&
804 "characteristics), in gridpoints per timestep. This is only "//&
805 "used if one of the open boundary segments is using Orlanski.", &
8060 units="nondim", default=1.0)
807 call get_param(param_file, mdl, "OBC_RAD_VEL_WT", OBC%gamma_uv, &
808 "The relative weighting for the baroclinic radiation "//&
809 "velocities (or speed of characteristics) at the new "//&
810 "time level (1) or the running mean (0) for velocities. "//&
811 "Valid values range from 0 to 1. This is only used if "//&
812 "one of the open boundary segments is using Orlanski.", &
8130 units="nondim", default=0.3)
814 endif
815
8160 Lscale_in = 0.
8170 Lscale_out = 0.
8180 if (open_boundary_query(OBC, apply_open_OBC=.true.)) then
819 call get_param(param_file, mdl, "OBC_TRACER_RESERVOIR_LENGTH_SCALE_OUT ", Lscale_out, &
820 "An effective length scale for restoring the tracer concentration "//&
821 "at the boundaries to externally imposed values when the flow "//&
8220 "is exiting the domain.", units="m", default=0.0, scale=US%m_to_L)
823
824 call get_param(param_file, mdl, "OBC_TRACER_RESERVOIR_LENGTH_SCALE_IN ", Lscale_in, &
825 "An effective length scale for restoring the tracer concentration "//&
826 "at the boundaries to values from the interior when the flow "//&
8270 "is entering the domain.", units="m", default=0.0, scale=US%m_to_L)
828 endif
829
8300 if (mask_outside) call mask_outside_OBCs(G, US, param_file, OBC)
831
832 ! All tracers are using the same restoring length scale for now, but we may want to make this
833 ! tracer-specific in the future for example, in cases where certain tracers are poorly constrained
834 ! by data while others are well constrained - MJH.
8350 do n=1,OBC%number_of_segments
8360 OBC%segment(n)%Tr_InvLscale_in = 0.0
8370 if (Lscale_in>0.) OBC%segment(n)%Tr_InvLscale_in = 1.0/Lscale_in
8380 OBC%segment(n)%Tr_InvLscale_out = 0.0
8390 if (Lscale_out>0.) OBC%segment(n)%Tr_InvLscale_out = 1.0/Lscale_out
840 enddo
841
8420 Lscale_in = 0.
8430 Lscale_out = 0.
8440 if (open_boundary_query(OBC, apply_open_OBC=.true.)) then
845 call get_param(param_file, mdl, "OBC_THICKNESS_RESERVOIR_LENGTH_SCALE_OUT ", Lscale_out, &
846 "An effective length scale for restoring the layer thickness "//&
847 "at the boundaries to externally imposed values when the flow "//&
8480 "is exiting the domain.", units="m", default=0.0, scale=US%m_to_L)
849
850 call get_param(param_file, mdl, "OBC_THICKNESS_RESERVOIR_LENGTH_SCALE_IN ", Lscale_in, &
851 "An effective length scale for restoring the layer thickness "//&
852 "at the boundaries to values from the interior when the flow "//&
8530 "is entering the domain.", units="m", default=0.0, scale=US%m_to_L)
854 endif
855
8560 do n=1,OBC%number_of_segments
8570 OBC%segment(n)%Th_InvLscale_in = 0.0
8580 if (Lscale_in>0.) OBC%segment(n)%Th_InvLscale_in = 1.0/Lscale_in
8590 OBC%segment(n)%Th_InvLscale_out = 0.0
8600 if (Lscale_out>0.) OBC%segment(n)%Th_InvLscale_out = 1.0/Lscale_out
8610 if (Lscale_in>0. .or. Lscale_out>0.) then
8620 if (OBC%segment(n)%is_E_or_W_2) then
8630 OBC%thickness_x_reservoirs_used = .true.
8640 OBC%use_h_res = .true.
865 else
8660 OBC%thickness_y_reservoirs_used = .true.
8670 OBC%use_h_res = .true.
868 endif
869 endif
870 enddo
871
872 call get_param(param_file, mdl, "REMAPPING_SCHEME", OBC%remappingScheme, &
8730 default=remappingDefaultScheme, do_not_log=.true.)
874 call get_param(param_file, mdl, "OBC_REMAPPING_SCHEME", OBC%remappingScheme, &
875 "This sets the reconstruction scheme used "//&
876 "for OBC vertical remapping for all variables. "//&
877 "It can be one of the following schemes: \n"//&
8780 trim(remappingSchemesDoc), default=OBC%remappingScheme)
879 call get_param(param_file, mdl, "FATAL_CHECK_RECONSTRUCTIONS", OBC%check_reconstruction, &
880 "If true, cell-by-cell reconstructions are checked for "//&
881 "consistency and if non-monotonicity or an inconsistency is "//&
8820 "detected then a FATAL error is issued.", default=.false., do_not_log=.true.)
883 call get_param(param_file, mdl, "FATAL_CHECK_REMAPPING", OBC%check_remapping, &
884 "If true, the results of remapping are checked for "//&
885 "conservation and new extrema and if an inconsistency is "//&
8860 "detected then a FATAL error is issued.", default=.false., do_not_log=.true.)
887 call get_param(param_file, mdl, "BRUSHCUTTER_MODE", OBC%brushcutter_mode, &
888 "If true, read external OBC data on the supergrid.", &
8890 default=.false.)
890 call get_param(param_file, mdl, "REMAP_BOUND_INTERMEDIATE_VALUES", OBC%force_bounds_in_subcell, &
891 "If true, the values on the intermediate grid used for remapping "//&
892 "are forced to be bounded, which might not be the case due to "//&
8930 "round off.", default=.false., do_not_log=.true.)
894 call get_param(param_file, mdl, "DEFAULT_ANSWER_DATE", default_answer_date, &
895 "This sets the default value for the various _ANSWER_DATE parameters.", &
8960 default=99991231)
897 call get_param(param_file, mdl, "REMAPPING_ANSWER_DATE", OBC%remap_answer_date, &
898 "The vintage of the expressions and order of arithmetic to use for remapping. "//&
899 "Values below 20190101 result in the use of older, less accurate expressions "//&
900 "that were in use at the end of 2018. Higher values result in the use of more "//&
901 "robust and accurate forms of mathematically equivalent expressions.", &
9020 default=default_answer_date)
903 call get_param(param_file, mdl, "REMAPPING_USE_OM4_SUBCELLS", OBC%om4_remap_via_sub_cells, &
9040 do_not_log=.true., default=.true.)
905
906 call get_param(param_file, mdl, "OBC_REMAPPING_USE_OM4_SUBCELLS", OBC%om4_remap_via_sub_cells, &
907 "If true, use the OM4 remapping-via-subcells algorithm for neutral diffusion. "//&
908 "See REMAPPING_USE_OM4_SUBCELLS for more details. "//&
9090 "We recommend setting this option to false.", default=OBC%om4_remap_via_sub_cells)
910
911 ! Safety check
9120 if ((OBC%open_u_BCs_exist_globally .or. OBC%open_v_BCs_exist_globally) .and. &
913 .not.G%symmetric ) call MOM_error(FATAL, &
914 "MOM_open_boundary, open_boundary_config: "//&
9150 "Symmetric memory must be used when using Flather OBCs.")
916 ! Need to do this last, because it depends on time_interp_external_init having already been called
9170 if (OBC%add_tide_constituents) then
9180 call initialize_obc_tides(OBC, US, param_file)
919 ! Tide update is done within update_OBC_segment_data, so this should be true if tides are included.
9200 OBC%update_OBC = .true.
921 endif
922
9230 if (.not.(OBC%specified_u_BCs_exist_globally .or. OBC%specified_v_BCs_exist_globally .or. &
924 OBC%open_u_BCs_exist_globally .or. OBC%open_v_BCs_exist_globally)) then
925 ! No open boundaries have been requested
9260 call open_boundary_dealloc(OBC)
927 endif
928
9290end subroutine open_boundary_config
930
931!> Setup vertical remapping for open boundaries
9320subroutine open_boundary_setup_vert(GV, US, OBC)
933 type(verticalGrid_type), intent(in) :: GV !< Container for vertical grid information
934 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
935 type(ocean_OBC_type), pointer :: OBC !< Open boundary control structure
936
937 ! Local variables
938 real :: dz_neglect, dz_neglect_edge ! Small thicknesses in vertical height units [Z ~> m]
939
9400 if (associated(OBC)) then
9410 if (OBC%number_of_segments > 0) then
942 ! Set up vertical remapping for open boundaries. Remapping happens independently on each PE,
943 ! so this block could be skipped for PEs without open boundary conditions that use remapping.
9440 if (GV%Boussinesq .and. (OBC%remap_answer_date < 20190101)) then
9450 dz_neglect = US%m_to_Z * 1.0e-30 ; dz_neglect_edge = US%m_to_Z * 1.0e-10
9460 elseif (GV%semi_Boussinesq .and. (OBC%remap_answer_date < 20190101)) then
9470 dz_neglect = GV%kg_m2_to_H*GV%H_to_Z * 1.0e-30 ; dz_neglect_edge = GV%kg_m2_to_H*GV%H_to_Z * 1.0e-10
948 else
9490 dz_neglect = GV%dZ_subroundoff ; dz_neglect_edge = GV%dZ_subroundoff
950 endif
9510 allocate(OBC%remap_z_CS)
952 call initialize_remapping(OBC%remap_z_CS, OBC%remappingScheme, boundary_extrapolation=.false., &
953 check_reconstruction=OBC%check_reconstruction, check_remapping=OBC%check_remapping, &
954 om4_remap_via_sub_cells=OBC%om4_remap_via_sub_cells, &
955 force_bounds_in_subcell=OBC%force_bounds_in_subcell, answer_date=OBC%remap_answer_date, &
9560 h_neglect=dz_neglect, h_neglect_edge=dz_neglect_edge)
9570 allocate(OBC%remap_h_CS)
958 call initialize_remapping(OBC%remap_h_CS, OBC%remappingScheme, boundary_extrapolation=.false., &
959 check_reconstruction=OBC%check_reconstruction, check_remapping=OBC%check_remapping, &
960 om4_remap_via_sub_cells=OBC%om4_remap_via_sub_cells, &
961 force_bounds_in_subcell=OBC%force_bounds_in_subcell, answer_date=OBC%remap_answer_date, &
9620 h_neglect=GV%H_subroundoff, h_neglect_edge=GV%H_subroundoff)
963 endif
964 endif
965
9660end subroutine open_boundary_setup_vert
967
968!> Determine which physical fields are required for this segment based on boundary-condition type
969!! and segment orientation. Also enable groups of physical fields required by tides or thermodynamics.
970!! Note the tidal group could be further narrowed based on modes.
9710subroutine segment_determine_required_fields(segment, tides, temp_salt)
972 type(OBC_segment_type), intent(inout) :: segment !< OBC segment
973 logical, optional, intent(in) :: tides !< Switch for tidal variables
974 logical, optional, intent(in) :: temp_salt !< Switch for thermodynamic variables
975
976 ! Local variables
977 logical :: use_tide ! Local switch for tidal variables
978 logical :: use_temp ! Local switch for thermodynamic variables
979 integer :: m
980 integer :: F_Vn, F_Vt, F_G
981 integer, parameter :: &
982 tide_idx(6) = (/ F_UAMP, F_UPHASE, F_VAMP, F_VPHASE, F_ZAMP, F_ZPHASE /), & ! Indices for tides
983 temp_idx(2) = (/ F_T, F_S /) ! Indices for thermodynamics
984
9850 if (.not. associated(segment%field)) &
9860 call MOM_error(FATAL, 'segment_determine_required_fields: segment%field is not allocated.')
987
9880 use_tide = .false. ; if (present(tides)) use_tide = tides
9890 use_temp = .false. ; if (present(temp_salt)) use_temp = temp_salt
990
991 ! Normal, tangential and gradient depend on segment orientation.
9920 if (segment%is_E_or_W_2) then
9930 F_Vn = F_U ; F_Vt = F_V ; F_G = F_VX
994 else
9950 F_Vn = F_V ; F_Vt = F_U ; F_G = F_UY
996 endif
9970 if (segment%Flather) &
9980 segment%field(F_Z)%required = .true.
999
10000 if (segment%Flather .or. segment%nudged .or. segment%specified) &
10010 segment%field(F_Vn)%required = .true.
1002
10030 if (segment%nudged_tan .or. segment%specified_tan) &
10040 segment%field(F_Vt)%required = .true.
1005
10060 if (segment%nudged_grad .or. segment%specified_grad) &
10070 segment%field(F_G)%required = .true.
1008
10090 if (use_tide) then ; do m = 1, size(tide_idx)
10100 segment%field(tide_idx(m))%required = .true.
1011 enddo ; endif
1012
10130 if (use_temp) then ; do m = 1, size(temp_idx)
10140 segment%field(temp_idx(m))%required = .true.
1015 enddo ; endif
1016
10170end subroutine segment_determine_required_fields
1018
1019!> Find physical field index from name
10200integer function find_phys_field_index(name)
1021 character(len=*), intent(in) :: name !< Field name
1022
1023 ! Local variables
1024 integer :: i
1025
10260 find_phys_field_index = 0
10270 do i = 1, NUM_PHYS_FIELDS ; if (trim(name) == PHYS_FIELD_NAMES(i)) then
10280 find_phys_field_index = i
10290 return
1030 endif ; enddo
10310end function find_phys_field_index
1032
1033!> Set global flag OBC%any_needs_IO_for_data.
10340subroutine OBC_any_IO(OBC)
1035 type(ocean_OBC_type), intent(inout) :: OBC !< Open boundary control structure
1036
1037 ! Local variables
1038 integer :: m, n
1039 logical :: use_IO
1040
10410 use_IO = .false.
10420 do n=1,OBC%number_of_segments
10430 do m=1,OBC%segment(n)%num_fields
10440 if (OBC%segment(n)%field(m)%use_IO) then
10450 use_IO = .true.
10460 exit
1047 endif
1048 enddo
10490 if (use_IO) exit
1050 enddo
1051
10520 OBC%any_needs_IO_for_data = any_across_PEs(use_IO)
10530end subroutine OBC_any_IO
1054
1055!> Allocate data (buffer_src, buffer_dst and dz_src) for a field at an OBC segment.
10560subroutine allocate_segment_field_data(field, OBC, segment, US, inputdir, filename, varname, &
1057 suffix, value, turns, nz)
1058 type(OBC_segment_data_type), &
1059 intent(inout) :: field !< A field of the segment
1060 type(ocean_OBC_type), intent(in) :: OBC !< Open boundary control structure
1061 type(OBC_segment_type), intent(inout) :: segment !< Segment to work on
1062 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
1063 character(len=*), intent(in) :: inputdir !< The directory of input files
1064 character(len=*), intent(in) :: filename !< Input file name
1065 character(len=*), intent(in) :: varname !< Variable name in the input file
1066 character(len=*), intent(in) :: suffix !< Variable name suffix, "_segment_xxx"
1067 real, intent(in) :: value !< Unscaled specified value of the field [a]
1068 integer, intent(in) :: turns !< Number of quarter turns of the grid
1069 integer, intent(in) :: nz !< Default k-axis size in buffer_dst
1070
1071 ! Local variables
1072 character(len=256) :: full_filename, full_varname ! Full filename and varname
1073 character(len=512) :: mesg ! Error message
1074 real :: init_value_dst ! Initial value for allocated buffer_dst array [a]
1075 integer :: qturns ! The number of quarter turns in the range of 0 to 3
1076 integer :: isd, ied, jsd, jed, IsdB, IedB, JsdB, JedB ! Aliases of segment geometry indices
1077 integer, dimension(4) :: siz, siz_check ! Four-dimensional shape of a variable in input file
1078 integer :: dim ! Loop index for siz/siz_check
1079 integer :: nk_dst ! k-axis size of buffer_dst
1080
10810 if (.not. segment%on_pe) return
1082
10830 isd = segment%HI%isd ; ied = segment%HI%ied ; IsdB = segment%HI%IsdB ; IedB = segment%HI%IedB
10840 jsd = segment%HI%jsd ; jed = segment%HI%jed ; JsdB = segment%HI%JsdB ; JedB = segment%HI%JedB
10850 nk_dst = nz
1086
10870 qturns = modulo(turns, 4)
1088
10890 field%on_face = field_is_on_face(field%name, segment%is_E_or_W)
1090 ! The scale factor for tracers may also be set in register_segment_tracer, and a constant input
1091 ! value is rescaled there.
10920 field%scale = scale_factor_from_name(field%name, US, segment%tr_Reg)
10930 field%use_IO = (trim(filename) /= 'none')
1094
10950 if (field%use_IO) then
10960 full_filename = trim(inputdir) // trim(filename)
10970 full_varname = trim(varname) // trim(suffix)
1098
10990 if (.not.file_exists(full_filename)) &
11000 call MOM_error(FATAL," Unable to open OBC file " // trim(full_filename))
1101
11020 call field_size(full_filename, full_varname, siz, no_domain=.true.)
11030 field%nk_src = siz(3)
1104
11050 if (OBC%brushcutter_mode .and. (modulo(siz(1),2) == 0 .or. modulo(siz(2),2) == 0)) then
11060 write(mesg, '("Brushcutter mode sizes ",I0," ",I0)') siz(1), siz(2)
11070 call MOM_error(WARNING, mesg // " " // trim(full_filename) // " " // trim(full_varname))
11080 call MOM_error(FATAL,'segment data are not on the supergrid')
1109 endif
1110
1111 ! Allocate src array
11120 if (.not.field%on_face) then
11130 allocate(field%buffer_src(IsdB:IedB, JsdB:JedB, field%nk_src), source=0.0)
11140 elseif (segment%is_E_or_W) then
11150 allocate(field%buffer_src(IsdB:IedB, jsd:jed, field%nk_src), source=0.0)
1116 else
11170 allocate(field%buffer_src(isd:ied, JsdB:JedB, field%nk_src), source=0.0)
1118 endif
1119
1120 field%handle = init_external_field(trim(full_filename), trim(full_varname), &
11210 ignore_axis_atts=.true., threading=SINGLE_FILE)
1122
11230 if ((field%nk_src > 1) .and. (.not. field_is_tidal(field%name))) then ! nk_src is depth
11240 full_varname = 'dz_' // trim(full_varname)
11250 call field_size(full_filename, full_varname, siz_check, no_domain=.true.)
11260 do dim = 1, 4 ; if (siz(dim) /= siz_check(dim)) &
1127 call MOM_error(FATAL, "'dz' field size is inconsistent with "//&
11280 "its corresponding variable.")
1129 enddo
1130
11310 if (.not.field%on_face) then
11320 allocate(field%dz_src(IsdB:IedB, JsdB:JedB, field%nk_src), source=0.0)
11330 elseif (segment%is_E_or_W) then
11340 allocate(field%dz_src(IsdB:IedB, jsd:jed, field%nk_src), source=0.0)
1135 else
11360 allocate(field%dz_src(isd:ied, JsdB:JedB, field%nk_src), source=0.0)
1137 endif
1138 field%dz_handle = init_external_field(trim(full_filename), trim(full_varname), &
11390 ignore_axis_atts=.true., threading=SINGLE_FILE)
1140
11410 elseif (field_is_tidal(field%name)) then ! nk_src is constituent for tidal variables
1142 ! expect third dimension to be number of constituents in MOM_input
11430 if (OBC%add_tide_constituents .and. (field%nk_src /= OBC%n_tide_constituents)) &
1144 call MOM_error(FATAL, 'Number of constituents in input data is not '//&
11450 'the same as the number specified')
11460 nk_dst = field%nk_src
1147
1148 else ! nk_src = 1
11490 nk_dst = 1
1150
1151 endif
1152
11530 init_value_dst = 0.0
1154 else ! This data is not being read from a file.
11550 field%value = field%scale * value
1156 ! Change the sign of the specified velocities, depending on the number of quarter turns of the grid.
1157 if ( ( ((field%name == 'U') .or. (field%name == 'Uamp')) .and. &
11580 ((qturns == 1) .or. (qturns == 2)) ) .or. &
1159 ( ((field%name == 'V') .or. (field%name == 'Vamp')) .and. &
1160 ((qturns == 3) .or. (qturns == 2)) ) ) &
11610 field%value = -field%value
1162
1163 ! Check if this is a tidal field. If so, the number of expected constituents must be 1.
11640 if (field_is_tidal(field%name)) then
11650 if (OBC%add_tide_constituents .and. (OBC%n_tide_constituents > 1)) &
1166 call MOM_error(FATAL, 'Only one constituent is supported when specifying '//&
11670 'tidal boundary conditions by value rather than file.')
11680 nk_dst = 1
1169 endif
1170
11710 if (field%name == 'SSH') &
11720 nk_dst = 1
1173
11740 init_value_dst = field%value
1175 endif
1176
1177 ! Allocate buffer_dst array
11780 if (.not.field%on_face) then
11790 allocate(field%buffer_dst(IsdB:IedB, JsdB:JedB, nk_dst), source=init_value_dst)
11800 elseif (segment%is_E_or_W) then
11810 allocate(field%buffer_dst(IsdB:IedB, jsd:jed, nk_dst), source=init_value_dst)
1182 else
11830 allocate(field%buffer_dst(isd:ied, JsdB:JedB, nk_dst), source=init_value_dst)
1184 endif
1185
1186 ! This can be removed.
11870 if (field%name == 'TEMP') segment%temp_segment_data_exists = .true.
11880 if (field%name == 'SALT') segment%salt_segment_data_exists = .true.
1189
11900end subroutine allocate_segment_field_data
1191
1192!> Get and store properties about the fields on the OBC segments and allocate space for reading
1193!! OBC data from files. In the process, it does funky stuff with the MPI processes.
11940subroutine initialize_segment_data(GV, US, OBC, PF, turns, use_temperature)
1195 type(verticalGrid_type), intent(in) :: GV !< Container for vertical grid information
1196 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
1197 type(ocean_OBC_type), target, intent(inout) :: OBC !< Open boundary control structure
1198 type(param_file_type), intent(in) :: PF !< Parameter file handle
1199 integer, intent(in) :: turns !< Number of quarter turns of the grid
1200 logical, intent(in) :: use_temperature !< If true, temperature and
1201 !! salinity used as state variables.
1202
1203 ! Local variables
1204 integer :: n, n_seg, m, num_manifest_fields, mm
1205 character(len=1024) :: segstr
1206 character(len=256) :: filename
1207 character(len=20) :: segname, suffix
1208 character(len=32) :: varname
1209 real :: value ! A value that is parsed from the segment data string [various units]
1210 character(len=32), dimension(NUM_PHYS_FIELDS) :: phys_inputs ! input physical field names
1211 integer, dimension(NUM_PHYS_FIELDS) :: phys_idx ! input physical field indices to PHYS_FIELD_NAMES
1212 character(len=32) :: bgc_input ! segment field names
1213 character(len=128) :: inputdir
1214 type(OBC_segment_type), pointer :: segment => NULL() ! pointer to segment type list
1215 character(len=256) :: mesg ! Message for error messages.
12160 integer, dimension(:), allocatable :: saved_pelist
1217 integer :: current_pe
1218 integer, dimension(1) :: single_pelist
1219 type(external_tracers_segments_props), pointer :: obgc_segments_props_list =>NULL()
1220 logical :: check_ts_needed ! Check if temperature and salinity are explicitly specified.
1221 integer :: idx
1222 character(len=256) :: routine_name ! Name of this subroutine
1223
12240 if (OBC%user_BCs_set_globally) return
1225
12260 routine_name = trim(mdl) // ', initialize_segment_data'
1227
12280 OBC%update_OBC = .true. ! Data is time-dependent if not using user BC.
1229
12300 check_ts_needed = use_temperature .and. (.not. OBC%ts_needed_bug)
1231
12320 call get_param(PF, mdl, "INPUTDIR", inputdir, default=".")
12330 inputdir = slasher(inputdir)
1234
1235 ! Try this here just for the documentation. It is repeated below.
12360 do n=1,OBC%number_of_segments
12370 write(segname, "('OBC_SEGMENT_',i3.3,'_DATA')") n
12380 call get_param(PF, mdl, segname, segstr, 'OBC segment docs')
1239 enddo
1240
1241 !< temporarily disable communication in order to read segment data independently
1242
12430 allocate(saved_pelist(0:num_PEs()-1))
12440 call Get_PElist(saved_pelist)
12450 current_pe = PE_here()
12460 single_pelist(1) = current_pe
12470 call Set_PElist(single_pelist)
1248
12490 do n=1,OBC%number_of_segments
12500 n_seg = n ; if (OBC%reverse_segment_order) n_seg = OBC%number_of_segments + 1 - n
12510 segment => OBC%segment(n_seg)
1252
12530 if (.not. segment%on_pe) cycle
1254
12550 write(segname, "('OBC_SEGMENT_',i3.3,'_DATA')") n
12560 write(suffix, "('_segment_',i3.3)") n
1257 ! needs documentation !! Yet, unsafe for now, causes grief for
1258 ! MOM_parameter_docs in circle_obcs on two processes.
1259 ! call get_param(PF, mdl, segname, segstr, 'xyz')
1260 ! Clear out any old values
12610 segstr = ''
12620 call get_param(PF, mdl, segname, segstr)
12630 if (segstr == '') then
12640 write(mesg,'("No OBC_SEGMENT_XXX_DATA string for OBC segment ",I0)') n
12650 call MOM_error(FATAL, mesg)
1266 endif
1267
12680 segment%num_fields = NUM_PHYS_FIELDS + OBC%num_obgc_tracers
12690 allocate(segment%field(segment%num_fields))
1270
1271 ! Initialize physical fields
12720 do m = 1, NUM_PHYS_FIELDS
12730 segment%field(m)%name = PHYS_FIELD_NAMES(m) ! The order of physical fields is fixed.
12740 segment%field(m)%bgc_tracer = .false.
12750 segment%field(m)%required = .false.
12760 segment%field(m)%use_IO = .false.
12770 segment%field(m)%tr_index = -1
1278 enddo
12790 segment%field(F_T)%tr_index = 1 ! Temperature tracer index is hard-coded.
12800 segment%field(F_S)%tr_index = 2 ! Salinity tracer index is hard-coded.
1281
1282 call segment_determine_required_fields(segment, tides=OBC%add_tide_constituents, &
12830 temp_salt=check_ts_needed)
1284
1285 ! Parse and find available physical fields
12860 call parse_segment_manifest_str(trim(segstr), num_manifest_fields, phys_inputs)
1287
12880 phys_idx(:) = -1
12890 do m = 1, num_manifest_fields
12900 idx = find_phys_field_index(rotated_field_name(trim(phys_inputs(m)), turns))
12910 if (idx == 0) then
12920 write(mesg,'("OBC segment ",I0," has an unknown input field: ",a)') n, trim(phys_inputs(m))
12930 call MOM_error(FATAL, trim(routine_name) // ", " // trim(mesg))
1294 endif
12950 if ((.not. segment%field(idx)%required) .and. &
1296 ((.not. (idx == F_T .or. idx == F_S)) .or. check_ts_needed)) then
1297 write(mesg,'("OBC segment ",I0," has an unnecessary field: ",a)') &
12980 n, trim(phys_inputs(m))
12990 call MOM_error(WARNING, trim(mesg))
1300 ! Unnecessary field is allowed and allocated for now.
1301 ! Otherwise, the next line can be uncommented.
1302 ! cycle
1303 endif
13040 phys_idx(idx) = m
1305 enddo
1306
1307 ! These can be removed.
13080 segment%temp_segment_data_exists = .false.
13090 segment%salt_segment_data_exists = .false.
1310
1311 ! Allocate physical fields
13120 do m = 1, NUM_PHYS_FIELDS
13130 if (segment%field(m)%required .and. (phys_idx(m) < 0)) then
13140 write(mesg,'("OBC segment ",I0," requires field: ",a)') n, trim(segment%field(m)%name)
13150 call MOM_error(FATAL, trim(routine_name) // ", " // trim(mesg))
1316 endif
13170 if ((phys_idx(m) > 0)) then ! Field is found in input, even if not required
1318 call parse_segment_data_str(trim(segstr), phys_idx(m), trim(phys_inputs(phys_idx(m))), &
13190 value, filename, varname)
1320 call allocate_segment_field_data(segment%field(m), OBC, segment, US, &
13210 inputdir, filename, varname, suffix, value, turns, GV%ke)
1322 endif
1323 enddo
1324
1325 ! Allocate BGC tracer fields
13260 obgc_segments_props_list => OBC%obgc_segments_props ! pointer to the head node
13270 do m = NUM_PHYS_FIELDS+1, segment%num_fields
13280 segment%field(m)%bgc_tracer = .true.
1329 ! Query the obgc segment properties by traversing the linked list
1330 call get_obgc_segments_props(obgc_segments_props_list, bgc_input, filename, varname, &
13310 segment%field(m)%resrv_lfac_in, segment%field(m)%resrv_lfac_out)
1332 ! Make sure the obgc tracer is not specified in the MOM6 param file too.
13330 do mm=1,num_manifest_fields ; if (trim(bgc_input) == trim(phys_inputs(mm))) then
1334 write(mesg,'("Input parameter for OBC segment ",I0," contains a BGC tracer: ", A)') &
13350 n, trim(bgc_input)
13360 call MOM_error(FATAL, trim(routine_name) // ", " // trim(mesg))
1337 endif ; enddo
13380 segment%field(m)%name = rotated_field_name(bgc_input, turns)
13390 segment%field(m)%tr_index = get_tracer_index(segment, trim(segment%field(m)%name))
1340 call allocate_segment_field_data(segment%field(m), OBC, segment, US, &
13410 inputdir, filename, varname, suffix, 0.0, turns, GV%ke)
1342 enddo
1343
1344 ! write(stderr, '(A)') trim(suffix)//" segment checksum"
13450 if (OBC%debug) call chksum_OBC_segment_data(OBC%segment(n_seg), GV, US, OBC%nk_OBC_debug, n)
1346
1347 enddo ! n-loop for segments
1348
13490 call Set_PElist(saved_pelist)
1350
1351 ! Determine global IO data requirement patterns.
13520 call OBC_any_IO(OBC)
13530end subroutine initialize_segment_data
1354
1355!> Determine whether a particular field is descretized at the normal-velocity faces of an open
1356!! boundary condition segment.
13570logical function field_is_on_face(name, is_E_or_W)
1358 character(len=*), intent(in) :: name !< The OBC segment data name to interpret
1359 logical, intent(in) :: is_E_or_W !< This is true for an eastern or western open boundary condition
1360
13610 field_is_on_face = .true.
13620 if (is_E_or_W) then
13630 if ((name == 'V') .or. (name == 'Vamp') .or. (name == 'Vphase') .or. (name == 'DVDX')) &
13640 field_is_on_face = .false.
1365 else
13660 if ((name == 'U') .or. (name == 'Uamp') .or. (name == 'Uphase') .or. (name == 'DUDY')) &
13670 field_is_on_face = .false.
1368 endif
13690end function field_is_on_face
1370
1371!> Determine based on its name whether a particular field a barotropic tidal field, for which the
1372!! third dimension is the tidal constituent rather than a vertical axis
13730logical function field_is_tidal(name)
1374 character(len=*), intent(in) :: name !< The OBC segment data name to interpret
1375
13760 field_is_tidal = ((index(name, 'phase') > 0) .or. (index(name, 'amp') > 0))
13770end function field_is_tidal
1378
1379!> This subroutine sets the sign of the OBC%segnum_u and OBC%segnum_v arrays to indicate the
1380!! direction of the faces - positive for logically eastern or northern OBCs and neagative
1381!! for logically western or southern OBCs, or zero on non-OBC points. Also store information
1382!! about which orientations of OBCs ar on this PE and the range of indices within which the
1383!! various orientations of OBCs can be found on this PE.
13840subroutine set_segnum_signs(OBC, G)
1385 type(ocean_OBC_type), intent(inout) :: OBC !< Open boundary control structure, perhaps on a rotated grid.
1386 type(dyn_horgrid_type), intent(in) :: G !< Ocean grid structure used by OBC
1387
1388 integer :: i, j
1389
13900 OBC%u_OBCs_on_PE = .false. ; OBC%v_OBCs_on_PE = .false.
13910 do j=G%jsd,G%jed ; do I=G%IsdB,G%IedB
13920 OBC%segnum_u(I,j) = abs(OBC%segnum_u(I,j))
13930 if (abs(OBC%segnum_u(I,j)) > 0) then
13940 OBC%u_OBCs_on_PE = .true.
13950 if (OBC%segment(abs(OBC%segnum_u(I,j)))%direction == OBC_DIRECTION_W) &
13960 OBC%segnum_u(I,j) = -abs(OBC%segnum_u(I,j))
1397 endif
1398 enddo ; enddo
13990 do J=G%JsdB,G%JedB ; do i=G%isd,G%ied
14000 OBC%segnum_v(i,J) = abs(OBC%segnum_v(i,J))
14010 if (abs(OBC%segnum_v(i,J)) > 0) then
14020 OBC%v_OBCs_on_PE = .true.
14030 if (OBC%segment(abs(OBC%segnum_v(i,J)))%direction == OBC_DIRECTION_S) &
14040 OBC%segnum_v(i,J) = -abs(OBC%segnum_v(i,J))
1405 endif
1406 enddo ; enddo
1407
1408 ! Determine the maximum and minimum index range for various directions of OBC points on this PE
1409 ! by first setting these one point outside of the wrong side of the domain.
14100 OBC%Is_u_W_obc = G%IedB + 1 ; OBC%Ie_u_W_obc = G%IsdB - 1
14110 OBC%js_u_W_obc = G%jed + 1 ; OBC%je_u_W_obc = G%jsd - 1
14120 OBC%Is_u_E_obc = G%IedB + 1 ; OBC%Ie_u_E_obc = G%IsdB - 1
14130 OBC%js_u_E_obc = G%jed + 1 ; OBC%je_u_E_obc = G%jsd - 1
14140 OBC%is_v_S_obc = G%ied + 1 ; OBC%ie_v_S_obc = G%isd - 1
14150 OBC%Js_v_S_obc = G%JedB + 1 ; OBC%Je_v_S_obc = G%JsdB - 1
14160 OBC%is_v_N_obc = G%ied + 1 ; OBC%ie_v_N_obc = G%isd - 1
14170 OBC%Js_v_N_obc = G%JedB + 1 ; OBC%Je_v_N_obc = G%JsdB - 1
14180 OBC%v_N_OBCs_on_PE = .false. ; OBC%v_S_OBCs_on_PE = .false.
14190 OBC%u_E_OBCs_on_PE = .false. ; OBC%u_W_OBCs_on_PE = .false.
1420 ! Note that the loop ranges are reduced because outward facing OBCs can not be applied at edge points.
14210 do j=G%jsd,G%jed ; do I=G%IsdB,G%IedB-1
14220 if (OBC%segnum_u(I,j) < 0) then ! This point has OBC_DIRECTION_W.
14230 OBC%Is_u_W_obc = min(I, OBC%Is_u_W_obc) ; OBC%Ie_u_W_obc = max(I, OBC%Ie_u_W_obc)
14240 OBC%js_u_W_obc = min(j, OBC%js_u_W_obc) ; OBC%je_u_W_obc = max(j, OBC%je_u_W_obc)
14250 OBC%u_W_OBCs_on_PE = .true.
1426 endif
1427 enddo ; enddo
14280 do j=G%jsd,G%jed ; do I=G%IsdB+1,G%IedB
14290 if (OBC%segnum_u(I,j) > 0) then ! This point has OBC_DIRECTION_E.
14300 OBC%Is_u_E_obc = min(I, OBC%Is_u_E_obc) ; OBC%Ie_u_E_obc = max(I, OBC%Ie_u_E_obc)
14310 OBC%js_u_E_obc = min(j, OBC%js_u_E_obc) ; OBC%je_u_E_obc = max(j, OBC%je_u_E_obc)
14320 OBC%u_E_OBCs_on_PE = .true.
1433 endif
1434 enddo ; enddo
14350 do J=G%JsdB,G%JedB-1 ; do i=G%isd,G%ied
14360 if (OBC%segnum_v(i,J) < 0) then ! This point has OBC_DIRECTION_S.
14370 OBC%is_v_S_obc = min(i, OBC%is_v_S_obc) ; OBC%ie_v_S_obc = max(i, OBC%ie_v_S_obc)
14380 OBC%Js_v_S_obc = min(J, OBC%Js_v_S_obc) ; OBC%Je_v_S_obc = max(J, OBC%Je_v_S_obc)
14390 OBC%v_S_OBCs_on_PE = .true.
1440 endif
1441 enddo ; enddo
14420 do J=G%JsdB+1,G%JedB ; do i=G%isd,G%ied
14430 if (OBC%segnum_v(i,J) > 0) then ! This point has OBC_DIRECTION_N.
14440 OBC%is_v_N_obc = min(i, OBC%is_v_N_obc) ; OBC%ie_v_N_obc = max(i, OBC%ie_v_N_obc)
14450 OBC%Js_v_N_obc = min(J, OBC%Js_v_N_obc) ; OBC%Je_v_N_obc = max(J, OBC%Je_v_N_obc)
14460 OBC%v_N_OBCs_on_PE = .true.
1447 endif
1448 enddo ; enddo
1449
14500end subroutine set_segnum_signs
1451
1452!> Return an appropriate dimensional scaling factor for input data based on an OBC segment data
1453!! name [various ~> 1], or 1 for tracers or other fields that do not match one of the specified names.
1454!! Note that calls to register_segment_tracer can come before or after calls to scale_factor_from_name.
1455
14560real function scale_factor_from_name(name, US, Tr_Reg)
1457 character(len=*), intent(in) :: name !< The OBC segment data name to interpret
1458 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
1459 type(segment_tracer_registry_type), pointer :: Tr_Reg !< pointer to tracer registry for this segment
1460
1461 integer :: m
1462
14630 select case (trim(name))
14640 case ('U') ; scale_factor_from_name = US%m_s_to_L_T
14650 case ('V') ; scale_factor_from_name = US%m_s_to_L_T
14660 case ('Uamp') ; scale_factor_from_name = US%m_s_to_L_T
14670 case ('Vamp') ; scale_factor_from_name = US%m_s_to_L_T
14680 case ('DVDX') ; scale_factor_from_name = US%T_to_s
14690 case ('DUDY') ; scale_factor_from_name = US%T_to_s
14700 case ('SSH') ; scale_factor_from_name = US%m_to_Z
14710 case ('SSHamp') ; scale_factor_from_name = US%m_to_Z
14720 case default ; scale_factor_from_name = 1.0
1473 end select
1474
14750 if (associated(Tr_Reg) .and. (scale_factor_from_name == 1.0)) then
1476 ! Check for name matches with previously registered tracers.
14770 do m=1,Tr_Reg%ntseg
14780 if (uppercase(name) == uppercase(Tr_Reg%Tr(m)%name)) then
14790 scale_factor_from_name = Tr_Reg%Tr(m)%scale
14800 exit
1481 endif
1482 enddo
1483 endif
1484
14850end function scale_factor_from_name
1486
1487!> Initize parameters and fields related to the specification of tides at open boundaries.
14880subroutine initialize_obc_tides(OBC, US, param_file)
1489 type(ocean_OBC_type), intent(inout) :: OBC !< Open boundary control structure
1490 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
1491 type(param_file_type), intent(in) :: param_file !< Parameter file handle
1492 integer, dimension(3) :: tide_ref_date !< Reference date (t = 0) for tidal forcing (year, month, day).
1493 integer, dimension(3) :: nodal_ref_date !< Date to calculate nodal modulation for (year, month, day).
1494 character(len=50) :: tide_constituent_str !< List of tidal constituents to include on boundary.
1495 type(astro_longitudes) :: nodal_longitudes !< Solar and lunar longitudes for tidal forcing
1496 type(time_type) :: nodal_time !< Model time to calculate nodal modulation for.
1497 integer :: c !< Index to tidal constituent.
1498 logical :: tides !< True if astronomical tides are also used.
1499
1500 call get_param(param_file, mdl, "OBC_TIDE_CONSTITUENTS", tide_constituent_str, &
1501 "Names of tidal constituents being added to the open boundaries.", &
15020 fail_if_missing=.true.)
1503
1504 call get_param(param_file, mdl, "TIDES", tides, &
15050 "If true, apply tidal momentum forcing.", default=.false., do_not_log=.true.)
1506
1507 call get_param(param_file, mdl, "TIDE_USE_EQ_PHASE", OBC%add_eq_phase, &
1508 "If true, add the equilibrium phase argument to the specified tidal phases.", &
15090 old_name="OBC_TIDE_ADD_EQ_PHASE", default=.false., do_not_log=tides)
1510
1511 call get_param(param_file, mdl, "TIDE_ADD_NODAL", OBC%add_nodal_terms, &
1512 "If true, include 18.6 year nodal modulation in the boundary tidal forcing.", &
15130 old_name="OBC_TIDE_ADD_NODAL", default=.false., do_not_log=tides)
1514
1515 call get_param(param_file, mdl, "TIDE_REF_DATE", tide_ref_date, &
1516 "Reference date to use for tidal calculations and equilibrium phase.", &
15170 old_name="OBC_TIDE_REF_DATE", defaults=(/0, 0, 0/), do_not_log=tides)
1518
1519 call get_param(param_file, mdl, "TIDE_NODAL_REF_DATE", nodal_ref_date, &
1520 "Fixed reference date to use for nodal modulation of boundary tides.", &
15210 old_name="OBC_TIDE_NODAL_REF_DATE", defaults=(/0, 0, 0/), do_not_log=tides)
1522
15230 allocate(OBC%tide_names(OBC%n_tide_constituents))
15240 read(tide_constituent_str, *) OBC%tide_names
1525
1526 ! Set reference time (t = 0) for boundary tidal forcing.
15270 if (sum(tide_ref_date) == 0) then ! tide_ref_date defaults to 0.
15280 OBC%time_ref = set_date(1, 1, 1, 0, 0, 0)
1529 else
15300 if (.not. OBC%add_eq_phase) then
1531 ! If equilibrium phase argument is not added, the input phases
1532 ! should already be relative to the reference time.
15330 call MOM_mesg('OBC tidal phases will *not* be corrected with equilibrium arguments.')
1534 endif
15350 OBC%time_ref = set_date(tide_ref_date(1), tide_ref_date(2), tide_ref_date(3), 0, 0, 0)
1536 endif
1537
1538 ! Find relevant lunar and solar longitudes at the reference time
15390 if (OBC%add_eq_phase) call astro_longitudes_init(OBC%time_ref, OBC%tidal_longitudes)
1540
1541 ! If the nodal correction is based on a different time, initialize that.
1542 ! Otherwise, it can use N from the time reference.
15430 if (OBC%add_nodal_terms) then
15440 if (sum(nodal_ref_date) /= 0) then
1545 ! A reference date was provided for the nodal correction
15460 nodal_time = set_date(nodal_ref_date(1), nodal_ref_date(2), nodal_ref_date(3), 0, 0, 0)
15470 call astro_longitudes_init(nodal_time, nodal_longitudes)
15480 elseif (OBC%add_eq_phase) then
1549 ! Astronomical longitudes were already calculated for use in equilibrium phases,
1550 ! so use nodal longitude from that.
15510 nodal_longitudes = OBC%tidal_longitudes
1552 else
1553 ! Tidal reference time is a required parameter, so calculate the longitudes from that.
15540 call astro_longitudes_init(OBC%time_ref, nodal_longitudes)
1555 endif
1556 endif
1557
15580 allocate(OBC%tide_frequencies(OBC%n_tide_constituents))
15590 allocate(OBC%tide_eq_phases(OBC%n_tide_constituents))
15600 allocate(OBC%tide_fn(OBC%n_tide_constituents))
15610 allocate(OBC%tide_un(OBC%n_tide_constituents))
1562
15630 do c=1,OBC%n_tide_constituents
1564 ! If tidal frequency is overridden by setting TIDE_*_FREQ, use that, otherwise use the
1565 ! default realistic frequency for this constituent.
1566 call get_param(param_file, mdl, "TIDE_"//trim(OBC%tide_names(c))//"_FREQ", OBC%tide_frequencies(c), &
1567 "Frequency of the "//trim(OBC%tide_names(c))//" tidal constituent. "//&
1568 "This is only used if TIDES and TIDE_"//trim(OBC%tide_names(c))// &
1569 " are true, or if OBC_TIDE_N_CONSTITUENTS > 0 and "//trim(OBC%tide_names(c))//&
1570 " is in OBC_TIDE_CONSTITUENTS.", &
15710 units="rad s-1", default=tidal_frequency(trim(OBC%tide_names(c))), scale=US%T_to_s)
1572
1573 ! Find equilibrium phase if needed
15740 if (OBC%add_eq_phase) then
15750 OBC%tide_eq_phases(c) = eq_phase(trim(OBC%tide_names(c)), OBC%tidal_longitudes)
1576 else
15770 OBC%tide_eq_phases(c) = 0.0
1578 endif
1579
1580 ! Find nodal corrections if needed
15810 if (OBC%add_nodal_terms) then
15820 call nodal_fu(trim(OBC%tide_names(c)), nodal_longitudes%N, OBC%tide_fn(c), OBC%tide_un(c))
1583 else
15840 OBC%tide_fn(c) = 1.0
15850 OBC%tide_un(c) = 0.0
1586 endif
1587 enddo
15880end subroutine initialize_obc_tides
1589
1590!> Define indices for segment and store in hor_index_type
1591!! using global segment bounds corresponding to q-points
15920subroutine setup_segment_indices(G, seg, Is_obc, Ie_obc, Js_obc, Je_obc)
1593 type(dyn_horgrid_type), intent(in) :: G !< grid type
1594 type(OBC_segment_type), intent(inout) :: seg !< Open boundary segment
1595 integer, intent(in) :: Is_obc !< Q-point global i-index of start of segment
1596 integer, intent(in) :: Ie_obc !< Q-point global i-index of end of segment
1597 integer, intent(in) :: Js_obc !< Q-point global j-index of start of segment
1598 integer, intent(in) :: Je_obc !< Q-point global j-index of end of segment
1599 ! Local variables
1600 integer :: IsgB, IegB, JsgB, JegB ! Global corner point indices at the ends of the OBC segments
1601 integer :: isg, ieg, jsg, jeg
1602
1603 ! Isg, Ieg will be I*_obc in global space
16040 if (Ie_obc < Is_obc) then
16050 IsgB = Ie_obc
16060 IegB = Is_obc
1607 else
16080 IsgB = Is_obc
16090 IegB = Ie_obc
1610 endif
1611
16120 if (Je_obc < Js_obc) then
16130 JsgB = Je_obc
16140 JegB = Js_obc
1615 else
16160 JsgB = Js_obc
16170 JegB = Je_obc
1618 endif
1619
1620 ! NOTE: h-points are defined along the interior of the segment q-points.
1621 ! For a given segment and its start and end index pairs, [IJ][se]gB, the
1622 ! h-cell corresponding to this pair are shown in the figure below.
1623 !
1624 ! x-x----------------x-x
1625 ! | | N | |
1626 ! x-x W E x-x
1627 ! | S |
1628 ! x-x----------------x-x
1629 ! | | | |
1630 ! x-x x-x
1631 !
1632 ! For segment points on the west and south, h-point indices are incremented
1633 ! in order to move to the interior cell.
1634
16350 if (Is_obc > Ie_obc) then
1636 ! Northern boundary
16370 isg = IsgB + 1
16380 jsg = JsgB
16390 ieg = IegB
16400 jeg = JegB
1641 endif
1642
16430 if (Is_obc < Ie_obc) then
1644 ! Southern boundary
16450 isg = IsgB + 1
16460 jsg = JsgB + 1
16470 ieg = IegB
16480 jeg = JegB + 1
1649 endif
1650
16510 if (Js_obc < Je_obc) then
1652 ! Eastern boundary
16530 isg = IsgB
16540 jsg = JsgB + 1
16550 ieg = IegB
16560 jeg = JegB
1657 endif
1658
16590 if (Js_obc > Je_obc) then
1660 ! Western boundary
16610 isg = IsgB + 1
16620 jsg = JsgB + 1
16630 ieg = IegB + 1
16640 jeg = JegB
1665 endif
1666
1667 ! Global space I*_obc but sorted
16680 seg%HI%IsgB = IsgB
16690 seg%HI%JegB = JegB
16700 seg%HI%IegB = IegB
16710 seg%HI%JsgB = JsgB
1672
16730 seg%HI%isg = isg
16740 seg%HI%jsg = jsg
16750 seg%HI%ieg = ieg
16760 seg%HI%jeg = jeg
1677
1678 ! Move into local index space
16790 IsgB = IsgB - G%idg_offset
16800 JsgB = JsgB - G%jdg_offset
16810 IegB = IegB - G%idg_offset
16820 JegB = JegB - G%jdg_offset
1683
16840 isg = isg - G%idg_offset
16850 jsg = jsg - G%jdg_offset
16860 ieg = ieg - G%idg_offset
16870 jeg = jeg - G%jdg_offset
1688
1689 ! This is the i-extent of the segment on this PE.
1690 ! The values are nonsense if the segment is not on this PE.
16910 seg%HI%IsdB = min(max(IsgB, G%HI%IsdB), G%HI%IedB)
16920 seg%HI%IedB = min(max(IegB, G%HI%IsdB), G%HI%IedB)
16930 seg%HI%isd = min(max(isg, G%HI%isd), G%HI%ied)
16940 seg%HI%ied = min(max(ieg, G%HI%isd), G%HI%ied)
16950 seg%HI%IscB = min(max(IsgB, G%HI%IscB), G%HI%IecB)
16960 seg%HI%IecB = min(max(IegB, G%HI%IscB), G%HI%IecB)
16970 seg%HI%isc = min(max(isg, G%HI%isc), G%HI%iec)
16980 seg%HI%iec = min(max(ieg, G%HI%isc), G%HI%iec)
1699
1700 ! This is the j-extent of the segment on this PE.
1701 ! The values are nonsense if the segment is not on this PE.
17020 seg%HI%JsdB = min(max(JsgB, G%HI%JsdB), G%HI%JedB)
17030 seg%HI%JedB = min(max(JegB, G%HI%JsdB), G%HI%JedB)
17040 seg%HI%jsd = min(max(jsg, G%HI%jsd), G%HI%jed)
17050 seg%HI%jed = min(max(jeg, G%HI%jsd), G%HI%jed)
17060 seg%HI%JscB = min(max(JsgB, G%HI%JscB), G%HI%JecB)
17070 seg%HI%JecB = min(max(JegB, G%HI%JscB), G%HI%JecB)
17080 seg%HI%jsc = min(max(jsg, G%HI%jsc), G%HI%jec)
17090 seg%HI%jec = min(max(jeg, G%HI%jsc), G%HI%jec)
1710
17110end subroutine setup_segment_indices
1712
1713!> Parse an OBC_SEGMENT_%%% string starting with "I=" and configure placement and type of OBC accordingly
17140subroutine setup_u_point_obc(OBC, G, US, segment_str, l_seg, l_seg_io, PF, reentrant_y)
1715 type(ocean_OBC_type), intent(inout) :: OBC !< Open boundary control structure
1716 type(dyn_horgrid_type), intent(in) :: G !< Ocean grid structure
1717 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
1718 character(len=*), intent(in) :: segment_str !< A string in form of "I=%,J=%:%,string"
1719 integer, intent(in) :: l_seg !< The internal segment number
1720 integer, intent(in) :: l_seg_io !< The segment number used for reading parameters
1721 type(param_file_type), intent(in) :: PF !< Parameter file handle
1722 logical, intent(in) :: reentrant_y !< is the domain reentrant in y?
1723 ! Local variables
1724 integer :: I_obc, Js_obc, Je_obc ! Position of segment in global index space
1725 integer :: j, a_loop
1726 character(len=32) :: action_str(8)
1727 character(len=128) :: segment_param_str
17280 real, allocatable, dimension(:) :: tnudge ! Nudging timescales [T ~> s]
1729 ! This returns the global indices for the segment
17300 call parse_segment_str(G%ieg, G%jeg, segment_str, I_obc, Js_obc, Je_obc, action_str, reentrant_y)
1731
17320 call setup_segment_indices(G, OBC%segment(l_seg),I_obc,I_obc,Js_obc,Je_obc)
1733
17340 I_obc = I_obc - G%idg_offset ! Convert to local tile indices on this tile
17350 Js_obc = Js_obc - G%jdg_offset ! Convert to local tile indices on this tile
17360 Je_obc = Je_obc - G%jdg_offset ! Convert to local tile indices on this tile
1737
17380 if (Je_obc>Js_obc) then
17390 OBC%segment(l_seg)%direction = OBC_DIRECTION_E
17400 elseif (Je_obc<Js_obc) then
17410 OBC%segment(l_seg)%direction = OBC_DIRECTION_W
17420 j = js_obc ; js_obc = je_obc ; je_obc = j
1743 endif
1744
17450 OBC%segment(l_seg)%on_pe = .false.
1746
17470 do a_loop = 1,8 ! up to 8 options available
17480 if (len_trim(action_str(a_loop)) == 0) then
17490 cycle
17500 elseif (trim(action_str(a_loop)) == 'FLATHER') then
17510 OBC%segment(l_seg)%Flather = .true.
17520 OBC%segment(l_seg)%open = .true.
17530 OBC%Flather_u_BCs_exist_globally = .true.
17540 OBC%open_u_BCs_exist_globally = .true.
17550 elseif (trim(action_str(a_loop)) == 'ORLANSKI') then
17560 OBC%segment(l_seg)%radiation = .true.
17570 OBC%segment(l_seg)%open = .true.
17580 OBC%open_u_BCs_exist_globally = .true.
17590 OBC%radiation_BCs_exist_globally = .true.
17600 elseif (trim(action_str(a_loop)) == 'ORLANSKI_TAN') then
17610 OBC%segment(l_seg)%radiation = .true.
17620 OBC%segment(l_seg)%radiation_tan = .true.
17630 OBC%radiation_BCs_exist_globally = .true.
17640 elseif (trim(action_str(a_loop)) == 'ORLANSKI_GRAD') then
17650 OBC%segment(l_seg)%radiation = .true.
17660 OBC%segment(l_seg)%radiation_grad = .true.
17670 elseif (trim(action_str(a_loop)) == 'OBLIQUE') then
17680 OBC%segment(l_seg)%oblique = .true.
17690 OBC%segment(l_seg)%open = .true.
17700 OBC%oblique_BCs_exist_globally = .true.
17710 OBC%open_u_BCs_exist_globally = .true.
17720 elseif (trim(action_str(a_loop)) == 'OBLIQUE_TAN') then
17730 OBC%segment(l_seg)%oblique = .true.
17740 OBC%segment(l_seg)%oblique_tan = .true.
17750 OBC%oblique_BCs_exist_globally = .true.
17760 elseif (trim(action_str(a_loop)) == 'OBLIQUE_GRAD') then
17770 OBC%segment(l_seg)%oblique = .true.
17780 OBC%segment(l_seg)%oblique_grad = .true.
17790 elseif (trim(action_str(a_loop)) == 'NUDGED') then
17800 OBC%segment(l_seg)%nudged = .true.
17810 OBC%nudged_u_BCs_exist_globally = .true.
17820 elseif (trim(action_str(a_loop)) == 'NUDGED_TAN') then
17830 OBC%segment(l_seg)%nudged_tan = .true.
17840 OBC%nudged_u_BCs_exist_globally = .true.
17850 elseif (trim(action_str(a_loop)) == 'NUDGED_GRAD') then
17860 OBC%segment(l_seg)%nudged_grad = .true.
17870 elseif (trim(action_str(a_loop)) == 'GRADIENT') then
17880 OBC%segment(l_seg)%gradient = .true.
17890 OBC%segment(l_seg)%open = .true.
17900 OBC%open_u_BCs_exist_globally = .true.
17910 elseif (trim(action_str(a_loop)) == 'SIMPLE') then
17920 OBC%segment(l_seg)%specified = .true.
17930 OBC%specified_u_BCs_exist_globally = .true. ! This avoids deallocation
17940 elseif (trim(action_str(a_loop)) == 'SIMPLE_TAN') then
17950 OBC%segment(l_seg)%specified_tan = .true.
17960 elseif (trim(action_str(a_loop)) == 'SIMPLE_GRAD') then
17970 OBC%segment(l_seg)%specified_grad = .true.
1798 else
1799 call MOM_error(FATAL, "MOM_open_boundary.F90, setup_u_point_obc: "//&
18000 "String '"//trim(action_str(a_loop))//"' not understood.")
1801 endif
18020 if (OBC%segment(l_seg)%nudged .or. OBC%segment(l_seg)%nudged_tan) then
18030 write(segment_param_str(1:43),"('OBC_SEGMENT_',i3.3,'_VELOCITY_NUDGING_TIMESCALES')") l_seg_io
18040 allocate(tnudge(2))
1805 call get_param(PF, mdl, segment_param_str(1:43), tnudge, &
1806 "Timescales in days for nudging along a segment, "//&
1807 "for inflow, then outflow. Setting both to zero should "//&
1808 "behave like SIMPLE obcs for the baroclinic velocities.", &
18090 fail_if_missing=.true., units="days", scale=86400.0*US%s_to_T)
18100 OBC%segment(l_seg)%Velocity_nudging_timescale_in = tnudge(1)
18110 OBC%segment(l_seg)%Velocity_nudging_timescale_out = tnudge(2)
18120 deallocate(tnudge)
1813 endif
1814
1815 enddo ! a_loop
1816
18170 OBC%segment(l_seg)%is_E_or_W_2 = .true.
1818
18190 if (I_obc<=G%HI%IsdB+1 .or. I_obc>=G%HI%IedB-1) return ! Boundary is not on tile
18200 if (Je_obc<=G%HI%JsdB .or. Js_obc>=G%HI%JedB) return ! Segment is not on tile
1821
18220 OBC%segment(l_seg)%on_pe = .true.
18230 OBC%segment(l_seg)%is_E_or_W = .true.
1824
18250 do j=G%HI%jsd, G%HI%jed
18260 if (j>Js_obc .and. j<=Je_obc) then
18270 OBC%segnum_u(I_obc,j) = l_seg
18280 if (OBC%segment(l_seg)%direction == OBC_DIRECTION_W) OBC%segnum_u(I_obc,j) = -l_seg
18290 OBC%u_OBCs_on_PE = .true.
1830 endif
1831 enddo
18320 OBC%segment(l_seg)%Is_obc = I_obc
18330 OBC%segment(l_seg)%Ie_obc = I_obc
18340 OBC%segment(l_seg)%Js_obc = Js_obc
18350 OBC%segment(l_seg)%Je_obc = Je_obc
18360 call allocate_OBC_segment_data(OBC, OBC%segment(l_seg))
1837
18380 if (OBC%segment(l_seg)%oblique .and. OBC%segment(l_seg)%radiation) &
1839 call MOM_error(FATAL, "MOM_open_boundary.F90, setup_u_point_obc: \n"//&
18400 "Orlanski and Oblique OBC options cannot be used together on one segment.")
18410end subroutine setup_u_point_obc
1842
1843!> Parse an OBC_SEGMENT_%%% string starting with "J=" and configure placement and type of OBC accordingly
18440subroutine setup_v_point_obc(OBC, G, US, segment_str, l_seg, l_seg_io, PF, reentrant_x)
1845 type(ocean_OBC_type), intent(inout) :: OBC !< Open boundary control structure
1846 type(dyn_horgrid_type), intent(in) :: G !< Ocean grid structure
1847 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
1848 character(len=*), intent(in) :: segment_str !< A string in form of "J=%,I=%:%,string"
1849 integer, intent(in) :: l_seg !< The internal segment number
1850 integer, intent(in) :: l_seg_io !< The segment number used for reading parameters
1851 type(param_file_type), intent(in) :: PF !< Parameter file handle
1852 logical, intent(in) :: reentrant_x !< is the domain reentrant in x?
1853 ! Local variables
1854 integer :: J_obc, Is_obc, Ie_obc ! Position of segment in global index space
1855 integer :: i, a_loop
1856 character(len=32) :: action_str(8)
1857 character(len=128) :: segment_param_str
18580 real, allocatable, dimension(:) :: tnudge ! Nudging timescales [T ~> s]
1859
1860 ! This returns the global indices for the segment
18610 call parse_segment_str(G%ieg, G%jeg, segment_str, J_obc, Is_obc, Ie_obc, action_str, reentrant_x)
1862
18630 call setup_segment_indices(G, OBC%segment(l_seg),Is_obc,Ie_obc,J_obc,J_obc)
1864
18650 J_obc = J_obc - G%jdg_offset ! Convert to local tile indices on this tile
18660 Is_obc = Is_obc - G%idg_offset ! Convert to local tile indices on this tile
18670 Ie_obc = Ie_obc - G%idg_offset ! Convert to local tile indices on this tile
1868
18690 if (Ie_obc>Is_obc) then
18700 OBC%segment(l_seg)%direction = OBC_DIRECTION_S
18710 elseif (Ie_obc<Is_obc) then
18720 OBC%segment(l_seg)%direction = OBC_DIRECTION_N
18730 i = Is_obc ; Is_obc = Ie_obc ; Ie_obc = i
1874 endif
1875
18760 OBC%segment(l_seg)%on_pe = .false.
1877
18780 do a_loop = 1,8
18790 if (len_trim(action_str(a_loop)) == 0) then
18800 cycle
18810 elseif (trim(action_str(a_loop)) == 'FLATHER') then
18820 OBC%segment(l_seg)%Flather = .true.
18830 OBC%segment(l_seg)%open = .true.
18840 OBC%Flather_v_BCs_exist_globally = .true.
18850 OBC%open_v_BCs_exist_globally = .true.
18860 elseif (trim(action_str(a_loop)) == 'ORLANSKI') then
18870 OBC%segment(l_seg)%radiation = .true.
18880 OBC%segment(l_seg)%open = .true.
18890 OBC%open_v_BCs_exist_globally = .true.
18900 OBC%radiation_BCs_exist_globally = .true.
18910 elseif (trim(action_str(a_loop)) == 'ORLANSKI_TAN') then
18920 OBC%segment(l_seg)%radiation = .true.
18930 OBC%segment(l_seg)%radiation_tan = .true.
18940 OBC%radiation_BCs_exist_globally = .true.
18950 elseif (trim(action_str(a_loop)) == 'ORLANSKI_GRAD') then
18960 OBC%segment(l_seg)%radiation = .true.
18970 OBC%segment(l_seg)%radiation_grad = .true.
18980 elseif (trim(action_str(a_loop)) == 'OBLIQUE') then
18990 OBC%segment(l_seg)%oblique = .true.
19000 OBC%segment(l_seg)%open = .true.
19010 OBC%oblique_BCs_exist_globally = .true.
19020 OBC%open_v_BCs_exist_globally = .true.
19030 elseif (trim(action_str(a_loop)) == 'OBLIQUE_TAN') then
19040 OBC%segment(l_seg)%oblique = .true.
19050 OBC%segment(l_seg)%oblique_tan = .true.
19060 OBC%oblique_BCs_exist_globally = .true.
19070 elseif (trim(action_str(a_loop)) == 'OBLIQUE_GRAD') then
19080 OBC%segment(l_seg)%oblique = .true.
19090 OBC%segment(l_seg)%oblique_grad = .true.
19100 elseif (trim(action_str(a_loop)) == 'NUDGED') then
19110 OBC%segment(l_seg)%nudged = .true.
19120 OBC%nudged_v_BCs_exist_globally = .true.
19130 elseif (trim(action_str(a_loop)) == 'NUDGED_TAN') then
19140 OBC%segment(l_seg)%nudged_tan = .true.
19150 OBC%nudged_v_BCs_exist_globally = .true.
19160 elseif (trim(action_str(a_loop)) == 'NUDGED_GRAD') then
19170 OBC%segment(l_seg)%nudged_grad = .true.
19180 elseif (trim(action_str(a_loop)) == 'GRADIENT') then
19190 OBC%segment(l_seg)%gradient = .true.
19200 OBC%segment(l_seg)%open = .true.
19210 OBC%open_v_BCs_exist_globally = .true.
19220 elseif (trim(action_str(a_loop)) == 'SIMPLE') then
19230 OBC%segment(l_seg)%specified = .true.
19240 OBC%specified_v_BCs_exist_globally = .true. ! This avoids deallocation
19250 elseif (trim(action_str(a_loop)) == 'SIMPLE_TAN') then
19260 OBC%segment(l_seg)%specified_tan = .true.
19270 elseif (trim(action_str(a_loop)) == 'SIMPLE_GRAD') then
19280 OBC%segment(l_seg)%specified_grad = .true.
1929 else
1930 call MOM_error(FATAL, "MOM_open_boundary.F90, setup_v_point_obc: "//&
19310 "String '"//trim(action_str(a_loop))//"' not understood.")
1932 endif
19330 if (OBC%segment(l_seg)%nudged .or. OBC%segment(l_seg)%nudged_tan) then
19340 write(segment_param_str(1:43),"('OBC_SEGMENT_',i3.3,'_VELOCITY_NUDGING_TIMESCALES')") l_seg_io
19350 allocate(tnudge(2))
1936 call get_param(PF, mdl, segment_param_str(1:43), tnudge, &
1937 "Timescales in days for nudging along a segment, "//&
1938 "for inflow, then outflow. Setting both to zero should "//&
1939 "behave like SIMPLE obcs for the baroclinic velocities.", &
19400 fail_if_missing=.true., units="days", scale=86400.0*US%s_to_T)
19410 OBC%segment(l_seg)%Velocity_nudging_timescale_in = tnudge(1)
19420 OBC%segment(l_seg)%Velocity_nudging_timescale_out = tnudge(2)
19430 deallocate(tnudge)
1944 endif
1945
1946 enddo ! a_loop
1947
19480 if (J_obc<=G%HI%JsdB+1 .or. J_obc>=G%HI%JedB-1) return ! Boundary is not on tile
19490 if (Ie_obc<=G%HI%IsdB .or. Is_obc>=G%HI%IedB) return ! Segment is not on tile
1950
19510 OBC%segment(l_seg)%on_pe = .true.
19520 OBC%segment(l_seg)%is_N_or_S = .true.
1953
19540 do i=G%HI%isd, G%HI%ied
19550 if (i>Is_obc .and. i<=Ie_obc) then
19560 OBC%segnum_v(i,J_obc) = l_seg
19570 if (OBC%segment(l_seg)%direction == OBC_DIRECTION_S) OBC%segnum_v(i,J_obc) = -l_seg
19580 OBC%v_OBCs_on_PE = .true.
1959 endif
1960 enddo
19610 OBC%segment(l_seg)%Is_obc = Is_obc
19620 OBC%segment(l_seg)%Ie_obc = Ie_obc
19630 OBC%segment(l_seg)%Js_obc = J_obc
19640 OBC%segment(l_seg)%Je_obc = J_obc
19650 call allocate_OBC_segment_data(OBC, OBC%segment(l_seg))
1966
19670 if (OBC%segment(l_seg)%oblique .and. OBC%segment(l_seg)%radiation) &
1968 call MOM_error(FATAL, "MOM_open_boundary.F90, setup_v_point_obc: \n"//&
19690 "Orlanski and Oblique OBC options cannot be used together on one segment.")
1970
19710end subroutine setup_v_point_obc
1972
1973!> Parse an OBC_SEGMENT_%%% string
19740subroutine parse_segment_str(ni_global, nj_global, segment_str, l, m, n, action_str, reentrant)
1975 integer, intent(in) :: ni_global !< Number of h-points in zonal direction
1976 integer, intent(in) :: nj_global !< Number of h-points in meridional direction
1977 character(len=*), intent(in) :: segment_str !< A string in form of "I=l,J=m:n,string" or "J=l,I=m,n,string"
1978 integer, intent(out) :: l !< The value of I=l, if segment_str begins with I=l, or the value of J=l
1979 integer, intent(out) :: m !< The value of J=m, if segment_str begins with I=, or the value of I=m
1980 integer, intent(out) :: n !< The value of J=n, if segment_str begins with I=, or the value of I=n
1981 character(len=*), intent(out) :: action_str(:) !< The "string" part of segment_str
1982 logical, intent(in) :: reentrant !< is domain reentrant in relevant direction?
1983 ! Local variables
1984 character(len=24) :: word1, word2, m_word, n_word !< Words delineated by commas in a string in form of
1985 !! "I=%,J=%:%,string"
1986 character(len=3) :: max_words !< maximum number of OBC types per segment
1987 integer :: l_max !< Either ni_global or nj_global, depending on whether segment_str begins with "I=" or "J="
1988 integer :: mn_max !< Either nj_global or ni_global, depending on whether segment_str begins with "I=" or "J="
1989 integer :: j
1990 integer, parameter :: halo = 10
1991
1992 ! Process first word which will started with either 'I=' or 'J='
19930 word1 = extract_word(segment_str,',',1)
19940 word2 = extract_word(segment_str,',',2)
19950 if (word1(1:2)=='I=') then
19960 l_max = ni_global
19970 mn_max = nj_global
19980 if (.not. (word2(1:2)=='J=')) call MOM_error(FATAL, "MOM_open_boundary.F90, parse_segment_str: "//&
19990 "Second word of string '"//trim(segment_str)//"' must start with 'J='.")
20000 elseif (word1(1:2)=='J=') then ! Note that the file_parser uniformly expands "=" to " = "
20010 l_max = nj_global
20020 mn_max = ni_global
20030 if (.not. (word2(1:2)=='I=')) call MOM_error(FATAL, "MOM_open_boundary.F90, parse_segment_str: "//&
20040 "Second word of string '"//trim(segment_str)//"' must start with 'I='.")
2005 else
2006 call MOM_error(FATAL, "MOM_open_boundary.F90, parse_segment_str: "//&
20070 "String '"//segment_str//"' must start with 'I=' or 'J='.")
2008 endif
2009
2010 ! Read l
20110 l = interpret_int_expr( word1(3:24), l_max )
20120 if (l<0 .or. l>l_max) then
2013 call MOM_error(FATAL, "MOM_open_boundary.F90, parse_segment_str: "//&
20140 "First value from string '"//trim(segment_str)//"' is outside of the physical domain.")
2015 endif
2016
2017 ! Read m
20180 m_word = extract_word(word2(3:24),':',1)
20190 m = interpret_int_expr( m_word, mn_max )
20200 if (reentrant) then
20210 if (m<-halo .or. m>mn_max+halo) then
2022 call MOM_error(FATAL, "MOM_open_boundary.F90, parse_segment_str: "//&
20230 "Beginning of range in string '"//trim(segment_str)//"' is outside of the physical domain.")
2024 endif
2025 else
20260 if (m<-1 .or. m>mn_max+1) then
2027 call MOM_error(FATAL, "MOM_open_boundary.F90, parse_segment_str: "//&
20280 "Beginning of range in string '"//trim(segment_str)//"' is outside of the physical domain.")
2029 endif
2030 endif
2031
2032 ! Read n
20330 n_word = extract_word(word2(3:24),':',2)
20340 n = interpret_int_expr( n_word, mn_max )
20350 if (reentrant) then
20360 if (n<-halo .or. n>mn_max+halo) then
2037 call MOM_error(FATAL, "MOM_open_boundary.F90, parse_segment_str: "//&
20380 "End of range in string '"//trim(segment_str)//"' is outside of the physical domain.")
2039 endif
2040 else
20410 if (n<-1 .or. n>mn_max+1) then
2042 call MOM_error(FATAL, "MOM_open_boundary.F90, parse_segment_str: "//&
20430 "End of range in string '"//trim(segment_str)//"' is outside of the physical domain.")
2044 endif
2045 endif
2046
20470 if (abs(n-m)==0) then
2048 call MOM_error(FATAL, "MOM_open_boundary.F90, parse_segment_str: "//&
20490 "Range in string '"//trim(segment_str)//"' must span one cell.")
2050 endif
2051
2052 ! checking if the number of provided OBC types is less than or equal to 8
20530 if (extract_word(segment_str,',',3+size(action_str))/="") then
20540 write(max_words, '(I0)') size(action_str)
2055 call MOM_error(FATAL, "MOM_open_boundary.F90, parse_segment_str: "// &
2056 "Number of OBC descriptor words in '" // trim(segment_str) // "' is too large. " // &
20570 "There can be at most " // trim(max_words) // " descriptor words.")
2058 endif
2059
2060 ! Type of open boundary condition
20610 do j = 1, size(action_str)
20620 action_str(j) = extract_word(segment_str,',',2+j)
2063 enddo
2064
2065 contains
2066
2067 ! Returns integer value interpreted from string in form of %I, N or N+-%I
20680 integer function interpret_int_expr(string, imax)
2069 character(len=*), intent(in) :: string !< Integer in form or %I, N or N-%I
2070 integer, intent(in) :: imax !< Value to replace 'N' with
2071 ! Local variables
2072 integer slen
2073
20740 slen = len_trim(string)
20750 if (slen==0) call MOM_error(FATAL, "MOM_open_boundary.F90, parse_segment_str: "//&
20760 "Parsed string was empty!")
20770 if (len_trim(string)==1 .and. string(1:1)=='N') then
20780 interpret_int_expr = imax
20790 elseif (string(1:1)=='N') then
20800 if (string(2:2)=='+') then
20810 read(string(3:slen),*,err=911) interpret_int_expr
20820 interpret_int_expr = imax + interpret_int_expr
20830 elseif (string(2:2)=='-') then
20840 read(string(3:slen),*,err=911) interpret_int_expr
20850 interpret_int_expr = imax - interpret_int_expr
2086 endif
2087 else
20880 read(string(1:slen),*,err=911) interpret_int_expr
2089 endif
20900 return
2091 911 call MOM_error(FATAL, "MOM_open_boundary.F90, parse_segment_str: "//&
20920 "Problem reading value from string '"//trim(string)//"'.")
20930 end function interpret_int_expr
2094end subroutine parse_segment_str
2095
2096
2097!> Parse an OBC_SEGMENT_%%%_DATA string and determine its fields
20980subroutine parse_segment_manifest_str(segment_str, num_fields, fields)
2099 character(len=*), intent(in) :: segment_str !< A string in form of
2100 !< "VAR1=file:foo1.nc(varnam1),VAR2=file:foo2.nc(varnam2),..."
2101 integer, intent(out) :: num_fields !< The number of fields in the segment data
2102 character(len=*), dimension(NUM_PHYS_FIELDS), intent(out) :: fields
2103 !< List of fieldnames for each segment
2104
2105 ! Local variables
2106 character(len=128) :: field_spec, field
2107 integer :: i
2108
21090 num_fields = 0
21100 fields(:) = ''
2111
21120 do
21130 field_spec = extract_word(segment_str, ',', num_fields + 1)
21140 if (trim(field_spec) == '') exit
2115
21160 if (num_fields >= NUM_PHYS_FIELDS) &
2117 call MOM_error(FATAL, "MOM_open_boundary.F90, parse_segment_manifest_str: " // &
21180 "too many fields in OBC segment manifest '" //trim(segment_str) // "'.")
2119
21200 field = trim(extract_word(field_spec, '=', 1))
2121
2122 ! Check for duplicate fields
21230 do i=1, num_fields
21240 if (fields(i) == trim(field)) &
2125 call MOM_error(FATAL, "MOM_open_boundary.F90, parse_segment_manifest_str: "//&
21260 "duplicate field '" // trim(field) // "' in '" // trim(segment_str) // "'.")
2127 enddo
2128
21290 num_fields = num_fields + 1
21300 fields(num_fields) = trim(field)
2131 enddo
21320end subroutine parse_segment_manifest_str
2133
2134
2135!> Parse an OBC_SEGMENT_%%%_DATA string
21360subroutine parse_segment_data_str(segment_str, idx, var, value, filename, fieldname)
2137 character(len=*), intent(in) :: segment_str !< A string in form of
2138 !! "VAR1=file:foo1.nc(varnam1),VAR2=file:foo2.nc(varnam2),..."
2139 integer, intent(in) :: idx !< Index of segment_str record
2140 character(len=*), intent(in) :: var !< The name of the variable for which parameters are needed
2141 character(len=*), intent(out) :: filename !< The name of the input file if using "file" method
2142 character(len=*), intent(out) :: fieldname !< The name of the variable in the input file if using
2143 !! "file" method
2144 real, optional, intent(out) :: value !< A constant value if using the "value" method in various
2145 !! units but without the internal rescaling [various units]
2146
2147 ! Local variables
2148 character(len=128) :: word1, word2, word3, method
2149 integer :: lword
2150
2151 ! Process first word which will start with the fieldname
21520 word3 = extract_word(segment_str, ',', idx)
21530 word1 = extract_word(word3, ':', 1)
2154 !if (trim(word1) == '') exit
21550 word2 = extract_word(word1, '=', 1)
21560 if (trim(word2) == trim(var)) then
21570 method = trim(extract_word(word1, '=', 2))
21580 lword = len_trim(method)
21590 if (method(lword-3:lword) == 'file') then
2160 ! raise an error id filename/fieldname not in argument list
21610 word1 = extract_word(word3, ':', 2)
21620 filename = extract_word(word1, '(', 1)
21630 fieldname = extract_word(word1, '(', 2)
21640 lword = len_trim(fieldname)
21650 fieldname = fieldname(1:lword-1) ! remove trailing parenth
21660 value = -999.
21670 elseif (method(lword-4:lword) == 'value') then
21680 filename = 'none'
21690 fieldname = 'none'
21700 word1 = extract_word(word3, ':', 2)
21710 lword = len_trim(word1)
21720 read(word1(1:lword), *, end=986, err=987) value
2173 endif
2174 endif
2175
21760 return
21770986 call MOM_error(FATAL,'End of record while parsing segment data specification! '//trim(segment_str))
21780987 call MOM_error(FATAL,'Error while parsing segment data specification! '//trim(segment_str))
21790end subroutine parse_segment_data_str
2180
2181!> Parse all the OBC_SEGMENT_%%%_DATA strings again
2182!! to see which need tracer reservoirs (all pes need to know).
21830subroutine parse_for_tracer_reservoirs(OBC, PF, use_temperature)
2184 type(ocean_OBC_type), target, intent(inout) :: OBC !< Open boundary control structure
2185 type(param_file_type), intent(in) :: PF !< Parameter file handle
2186 logical, intent(in) :: use_temperature !< If true, T and S are used
2187
2188 ! Local variables
2189 integer :: n ! The segment number used to read in input data
2190 integer :: n_seg ! The internal segment number
2191 integer :: m, num_fields ! Used to loop over the fields on a segment
2192 integer :: na
2193 character(len=1024) :: segstr
2194 character(len=256) :: filename
2195 character(len=20) :: segname, suffix
2196 character(len=32) :: fieldname
2197 real :: value ! A value that is parsed from the segment data string [various units]
2198 character(len=32), dimension(NUM_PHYS_FIELDS) :: fields ! segment field names
2199 type(OBC_segment_type), pointer :: segment => NULL() ! pointer to segment type list
2200
22010 do n=1,OBC%number_of_segments
22020 n_seg = n ; if (OBC%reverse_segment_order) n_seg = OBC%number_of_segments + 1 - n
22030 segment => OBC%segment(n_seg)
22040 write(segname, "('OBC_SEGMENT_',i3.3,'_DATA')") n
22050 write(suffix, "('_segment_',i3.3)") n
2206 ! Clear out any old values
22070 segstr = ''
22080 call get_param(PF, mdl, segname, segstr)
22090 if (segstr == '') cycle
2210
22110 call parse_segment_manifest_str(trim(segstr), num_fields, fields)
22120 if (num_fields == 0) cycle
2213
2214 ! At this point, just search for TEMP and SALT as tracers 1 and 2.
22150 do m=1,num_fields
22160 call parse_segment_data_str(trim(segstr), m, trim(fields(m)), value, filename, fieldname)
22170 if (trim(filename) /= 'none') then
22180 if (fields(m) == 'TEMP') then
22190 if (segment%is_E_or_W_2) then
22200 OBC%tracer_x_reservoirs_used(1) = .true.
2221 else
22220 OBC%tracer_y_reservoirs_used(1) = .true.
2223 endif
2224 endif
22250 if (fields(m) == 'SALT') then
22260 if (segment%is_E_or_W_2) then
22270 OBC%tracer_x_reservoirs_used(2) = .true.
2228 else
22290 OBC%tracer_y_reservoirs_used(2) = .true.
2230 endif
2231 endif
2232 endif
2233 enddo
2234 ! Alternately, set first two to true if use_temperature is true
22350 if (use_temperature) then
22360 if (segment%is_E_or_W_2) then
22370 OBC%tracer_x_reservoirs_used(1) = .true.
22380 OBC%tracer_x_reservoirs_used(2) = .true.
2239 else
22400 OBC%tracer_y_reservoirs_used(1) = .true.
22410 OBC%tracer_y_reservoirs_used(2) = .true.
2242 endif
2243 endif
2244 !Add reservoirs for external/obgc tracers
2245 !There is a diconnect in the above logic between tracer index and reservoir index.
2246 !It arbitarily assigns reservoir indexes 1&2 to tracers T&S,
2247 !So we need to start from reservoir index for non-native tracers from 3, hence na=2 below.
2248 !num_fields is the number of vars in segstr (6 of them now, U,V,SSH,TEMP,SALT,dye)
2249 !but OBC%tracer_x_reservoirs_used is allocated to size Reg%ntr, which is the total number of tracers
22500 na = 2 ! Number of native MOM6 tracers (T&S) with reservoirs
22510 do m=1,OBC%num_obgc_tracers
2252 !This logic assumes all external tarcers need a reservoir
2253 !The segments for tracers are not initialized yet (that happens later in initialize_segment_data())
2254 !so we cannot query to determine if this tracer needs a reservoir.
22550 if (segment%is_E_or_W_2) then
22560 OBC%tracer_x_reservoirs_used(m+na) = .true.
2257 else
22580 OBC%tracer_y_reservoirs_used(m+na) = .true.
2259 endif
2260 enddo
2261 enddo
2262
22630 return
2264
2265end subroutine parse_for_tracer_reservoirs
2266
2267!> Do any necessary halo updates on OBC-related fields.
22680subroutine open_boundary_halo_update(G, OBC)
2269 type(ocean_grid_type), intent(in) :: G !< Ocean grid structure
2270 type(ocean_OBC_type), pointer :: OBC !< Open boundary control structure
2271
2272 ! Local variables
2273 integer :: m
2274
22750 if (.not.associated(OBC)) return
2276
22770 id_clock_pass = cpu_clock_id('(Ocean OBC halo updates)', grain=CLOCK_ROUTINE)
22780 if (OBC%radiation_BCs_exist_globally) call pass_vector(OBC%rx_normal, OBC%ry_normal, G%Domain, &
22790 To_All+Scalar_Pair)
22800 if (OBC%oblique_BCs_exist_globally) then
2281! call pass_vector(OBC%rx_oblique_u, OBC%ry_oblique_v, G%Domain, To_All+Scalar_Pair)
2282! call pass_vector(OBC%ry_oblique_u, OBC%rx_oblique_v, G%Domain, To_All+Scalar_Pair)
2283! call pass_vector(OBC%cff_normal_u, OBC%cff_normal_v, G%Domain, To_All+Scalar_Pair)
22840 call create_group_pass(OBC%pass_oblique, OBC%rx_oblique_u, OBC%ry_oblique_v, G%Domain, To_All+Scalar_Pair)
22850 call create_group_pass(OBC%pass_oblique, OBC%ry_oblique_u, OBC%rx_oblique_v, G%Domain, To_All+Scalar_Pair)
22860 call create_group_pass(OBC%pass_oblique, OBC%cff_normal_u, OBC%cff_normal_v, G%Domain, To_All+Scalar_Pair)
22870 call do_group_pass(OBC%pass_oblique, G%Domain)
2288 endif
22890 if (allocated(OBC%tres_x) .and. allocated(OBC%tres_y)) then
22900 do m=1,OBC%ntr
22910 call pass_vector(OBC%tres_x(:,:,:,m), OBC%tres_y(:,:,:,m), G%Domain, To_All+Scalar_Pair)
2292 enddo
22930 elseif (allocated(OBC%tres_x)) then
22940 do m=1,OBC%ntr
22950 call pass_var(OBC%tres_x(:,:,:,m), G%Domain, position=EAST_FACE)
2296 enddo
22970 elseif (allocated(OBC%tres_y)) then
22980 do m=1,OBC%ntr
22990 call pass_var(OBC%tres_y(:,:,:,m), G%Domain, position=NORTH_FACE)
2300 enddo
2301 endif
23020 if (allocated(OBC%h_res_x) .and. allocated(OBC%h_res_y)) then
23030 call pass_vector(OBC%h_res_x(:,:,:), OBC%h_res_y(:,:,:), G%Domain, To_All+Scalar_Pair)
23040 elseif (allocated(OBC%h_res_x)) then
23050 call pass_var(OBC%h_res_x(:,:,:), G%Domain, position=EAST_FACE)
23060 elseif (allocated(OBC%h_res_y)) then
23070 call pass_var(OBC%h_res_y(:,:,:), G%Domain, position=NORTH_FACE)
2308 endif
2309
2310end subroutine open_boundary_halo_update
2311
23120logical function open_boundary_query(OBC, apply_open_OBC, apply_specified_OBC, apply_Flather_OBC, &
2313 apply_nudged_OBC, needs_ext_seg_data)
2314 type(ocean_OBC_type), pointer :: OBC !< Open boundary control structure
2315 logical, optional, intent(in) :: apply_open_OBC !< Returns True if open_*_BCs_exist_globally is true
2316 logical, optional, intent(in) :: apply_specified_OBC !< Returns True if specified_*_BCs_exist_globally is true
2317 logical, optional, intent(in) :: apply_Flather_OBC !< Returns True if Flather_*_BCs_exist_globally is true
2318 logical, optional, intent(in) :: apply_nudged_OBC !< Returns True if nudged_*_BCs_exist_globally is true
2319 logical, optional, intent(in) :: needs_ext_seg_data !< Returns True if external segment data needed
23200 open_boundary_query = .false.
23210 if (.not. associated(OBC)) return
23220 if (present(apply_open_OBC)) open_boundary_query = OBC%open_u_BCs_exist_globally .or. &
23230 OBC%open_v_BCs_exist_globally
23240 if (present(apply_specified_OBC)) open_boundary_query = OBC%specified_u_BCs_exist_globally .or. &
23250 OBC%specified_v_BCs_exist_globally
23260 if (present(apply_Flather_OBC)) open_boundary_query = OBC%Flather_u_BCs_exist_globally .or. &
23270 OBC%Flather_v_BCs_exist_globally
23280 if (present(apply_nudged_OBC)) open_boundary_query = OBC%nudged_u_BCs_exist_globally .or. &
23290 OBC%nudged_v_BCs_exist_globally
23300 if (present(needs_ext_seg_data)) open_boundary_query = OBC%any_needs_IO_for_data
2331
23320end function open_boundary_query
2333
2334!> Deallocate open boundary data
23350subroutine open_boundary_dealloc(OBC)
2336 type(ocean_OBC_type), pointer :: OBC !< Open boundary control structure
2337 type(OBC_segment_type), pointer :: segment => NULL()
2338 integer :: n
2339
23400 if (.not. associated(OBC)) return
2341
23420 do n=1,OBC%number_of_segments
23430 segment => OBC%segment(n)
23440 call deallocate_OBC_segment_data(segment)
2345 enddo
23460 if (allocated(OBC%segment)) deallocate(OBC%segment)
23470 if (allocated(OBC%segnum_u)) deallocate(OBC%segnum_u)
23480 if (allocated(OBC%segnum_v)) deallocate(OBC%segnum_v)
23490 if (allocated(OBC%rx_normal)) deallocate(OBC%rx_normal)
23500 if (allocated(OBC%ry_normal)) deallocate(OBC%ry_normal)
23510 if (allocated(OBC%rx_oblique_u)) deallocate(OBC%rx_oblique_u)
23520 if (allocated(OBC%ry_oblique_u)) deallocate(OBC%ry_oblique_u)
23530 if (allocated(OBC%rx_oblique_v)) deallocate(OBC%rx_oblique_v)
23540 if (allocated(OBC%ry_oblique_v)) deallocate(OBC%ry_oblique_v)
23550 if (allocated(OBC%cff_normal_u)) deallocate(OBC%cff_normal_u)
23560 if (allocated(OBC%cff_normal_v)) deallocate(OBC%cff_normal_v)
23570 if (allocated(OBC%tres_x)) deallocate(OBC%tres_x)
23580 if (allocated(OBC%tres_y)) deallocate(OBC%tres_y)
23590 if (allocated(OBC%h_res_x)) deallocate(OBC%h_res_x)
23600 if (allocated(OBC%h_res_y)) deallocate(OBC%h_res_y)
23610 if (associated(OBC%remap_z_CS)) deallocate(OBC%remap_z_CS)
23620 if (associated(OBC%remap_h_CS)) deallocate(OBC%remap_h_CS)
23630 deallocate(OBC)
2364end subroutine open_boundary_dealloc
2365
2366!> Close open boundary data
23670subroutine open_boundary_end(OBC)
2368 type(ocean_OBC_type), pointer :: OBC !< Open boundary control structure
23690 call open_boundary_dealloc(OBC)
23700end subroutine open_boundary_end
2371
2372!> Sets the slope of bathymetry normal to an open boundary to zero.
23730subroutine open_boundary_impose_normal_slope(OBC, G, depth)
2374 type(ocean_OBC_type), pointer :: OBC !< Open boundary control structure
2375 type(dyn_horgrid_type), intent(in) :: G !< Ocean grid structure
2376 real, dimension(SZI_(G),SZJ_(G)), intent(inout) :: depth !< Bathymetry at h-points, in [Z ~> m] or other units
2377 ! Local variables
2378 integer :: i, j, n
2379 type(OBC_segment_type), pointer :: segment => NULL()
2380
23810 if (.not.associated(OBC)) return
2382
23830 if (.not.(OBC%specified_u_BCs_exist_globally .or. OBC%specified_v_BCs_exist_globally .or. &
2384 OBC%open_u_BCs_exist_globally .or. OBC%open_v_BCs_exist_globally)) &
23850 return
2386
23870 do n=1,OBC%number_of_segments
23880 segment => OBC%segment(n)
23890 if (.not. segment%on_pe) cycle
23900 if (segment%direction == OBC_DIRECTION_E) then
23910 I=segment%HI%IsdB
23920 do j=segment%HI%jsd,segment%HI%jed
23930 depth(i+1,j) = depth(i,j)
2394 enddo
23950 elseif (segment%direction == OBC_DIRECTION_W) then
23960 I=segment%HI%IsdB
23970 do j=segment%HI%jsd,segment%HI%jed
23980 depth(i,j) = depth(i+1,j)
2399 enddo
24000 elseif (segment%direction == OBC_DIRECTION_N) then
24010 J=segment%HI%JsdB
24020 do i=segment%HI%isd,segment%HI%ied
24030 depth(i,j+1) = depth(i,j)
2404 enddo
24050 elseif (segment%direction == OBC_DIRECTION_S) then
24060 J=segment%HI%JsdB
24070 do i=segment%HI%isd,segment%HI%ied
24080 depth(i,j) = depth(i,j+1)
2409 enddo
2410 endif
2411 enddo
2412
2413end subroutine open_boundary_impose_normal_slope
2414
2415!> Reconcile masks and open boundaries, deallocate OBC on PEs where it is not needed.
2416!! Also adjust u- and v-point cell area on specified open boundaries and mask all
2417!! points outside open boundaries.
24180subroutine open_boundary_impose_land_mask(OBC, G, areaCu, areaCv, US)
2419 type(ocean_OBC_type), pointer :: OBC !< Open boundary control structure
2420 type(dyn_horgrid_type), intent(inout) :: G !< Ocean grid structure
2421 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
2422 real, dimension(SZIB_(G),SZJ_(G)), intent(inout) :: areaCu !< Area of a u-cell [L2 ~> m2]
2423 real, dimension(SZI_(G),SZJB_(G)), intent(inout) :: areaCv !< Area of a u-cell [L2 ~> m2]
2424 ! Local variables
2425 integer :: i, j, n
2426 type(OBC_segment_type), pointer :: segment => NULL()
2427 logical :: any_U, any_V
2428
24290 if (.not.associated(OBC)) return
2430
24310 do n=1,OBC%number_of_segments
24320 segment => OBC%segment(n)
24330 if (.not. segment%on_pe) cycle
24340 if (segment%is_E_or_W) then
2435 ! Sweep along u-segments and delete the OBC for blocked points.
2436 ! Also, mask all points outside.
24370 I=segment%HI%IsdB
24380 do j=segment%HI%jsd,segment%HI%jed
24390 if (G%mask2dCu(I,j) == 0) OBC%segnum_u(I,j) = 0
24400 if (segment%direction == OBC_DIRECTION_W) then
24410 G%mask2dT(i,j) = 0.0
2442 else
24430 G%mask2dT(i+1,j) = 0.0
2444 endif
2445 enddo
24460 do J=segment%HI%JsdB+1,segment%HI%JedB-1
24470 if (segment%direction == OBC_DIRECTION_W) then
24480 G%mask2dCv(i,J) = 0 ; G%OBCmaskCv(i,J) = 0.0 ; G%IdyCv_OBCmask(i,J) = 0.0
2449 else
24500 G%mask2dCv(i+1,J) = 0.0 ; G%OBCmaskCv(i+1,J) = 0.0 ; G%IdyCv_OBCmask(i+1,J) = 0.0
2451 endif
2452 enddo
2453 else
2454 ! Sweep along v-segments and delete the OBC for blocked points.
24550 J=segment%HI%JsdB
24560 do i=segment%HI%isd,segment%HI%ied
24570 if (G%mask2dCv(i,J) == 0) OBC%segnum_v(i,J) = 0
24580 if (segment%direction == OBC_DIRECTION_S) then
24590 G%mask2dT(i,j) = 0.0
2460 else
24610 G%mask2dT(i,j+1) = 0.0
2462 endif
2463 enddo
24640 do I=segment%HI%IsdB+1,segment%HI%IedB-1
24650 if (segment%direction == OBC_DIRECTION_S) then
24660 G%mask2dCu(I,j) = 0.0 ; G%OBCmaskCu(I,j) = 0.0 ; G%IdxCu_OBCmask(I,j) = 0.0
2467 else
24680 G%mask2dCu(I,j+1) = 0.0 ; G%OBCmaskCu(I,j+1) = 0.0 ; G%IdxCu_OBCmask(I,j+1) = 0.0
2469 endif
2470 enddo
2471 endif
2472 enddo
2473
24740 do n=1,OBC%number_of_segments
24750 segment => OBC%segment(n)
24760 if (.not. (segment%on_pe .and. segment%open)) cycle
2477 ! Set the OBCmask values to help eliminate certain terms at u- or v- OBC points.
2478 ! Testing suggests this could be applied at all u- or v- OBC points without changing answers.
24790 if (segment%is_E_or_W) then
24800 I=segment%HI%IsdB
24810 do j=segment%HI%jsd,segment%HI%jed
24820 G%OBCmaskCu(I,j) = 0.0 ; G%IdxCu_OBCmask(I,j) = 0.0
2483 enddo
2484 else
24850 J=segment%HI%JsdB
24860 do i=segment%HI%isd,segment%HI%ied
24870 G%OBCmaskCv(i,J) = 0.0 ; G%IdyCv_OBCmask(i,J) = 0.0
2488 enddo
2489 endif
2490 enddo
2491
24920 do n=1,OBC%number_of_segments
24930 segment => OBC%segment(n)
24940 if (.not. segment%on_pe .or. .not. segment%specified) cycle
24950 if (segment%is_E_or_W) then
2496 ! Sweep along u-segments and for %specified BC points reset the u-point area which was masked out
24970 I=segment%HI%IsdB
24980 do j=segment%HI%jsd,segment%HI%jed
24990 if (segment%direction == OBC_DIRECTION_E) then
25000 areaCu(I,j) = G%areaT(i,j) ! Both of these are in [L2 ~> m2]
2501 else ! West
25020 areaCu(I,j) = G%areaT(i+1,j) ! Both of these are in [L2 ~> m2]
2503 endif
2504 enddo
2505 else
2506 ! Sweep along v-segments and for %specified BC points reset the v-point area which was masked out
25070 J=segment%HI%JsdB
25080 do i=segment%HI%isd,segment%HI%ied
25090 if (segment%direction == OBC_DIRECTION_S) then
25100 areaCv(i,J) = G%areaT(i,j+1) ! Both of these are in [L2 ~> m2]
2511 else ! North
25120 areaCv(i,J) = G%areaT(i,j) ! Both of these are in [L2 ~> m2]
2513 endif
2514 enddo
2515 endif
2516 enddo
2517
2518 ! G%mask2du will be open wherever bathymetry allows it.
2519 ! Bathymetry outside of the open boundary was adjusted to match
2520 ! the bathymetry inside so these points will be open unless the
2521 ! bathymetry inside the boundary was too shallow and flagged as land.
25220 any_U = .false.
25230 any_V = .false.
25240 do n=1,OBC%number_of_segments
25250 segment => OBC%segment(n)
25260 if (.not. segment%on_pe) cycle
25270 if (segment%is_E_or_W) then
25280 I=segment%HI%IsdB
25290 do j=segment%HI%jsd,segment%HI%jed
25300 if (OBC%segnum_u(I,j) /= 0) any_U = .true.
2531 enddo
2532 else
25330 J=segment%HI%JsdB
25340 do i=segment%HI%isd,segment%HI%ied
25350 if (OBC%segnum_v(i,J) /= 0) any_V = .true.
2536 enddo
2537 endif
2538 enddo
2539
25400 OBC%u_OBCs_on_PE = any_U
25410 OBC%v_OBCs_on_PE = any_V
25420 OBC%OBC_pe = (any_U .or. any_V)
2543
2544end subroutine open_boundary_impose_land_mask
2545
2546!> Initialize the tracer reservoirs values, perhaps only if they have not been set via a restart file.
25470subroutine setup_OBC_tracer_reservoirs(G, GV, OBC, restart_CS)
2548 type(ocean_grid_type), intent(in) :: G !< Ocean grid structure
2549 type(verticalGrid_type), intent(in) :: GV !< The ocean's vertical grid structure
2550 type(ocean_OBC_type), target, intent(inout) :: OBC !< Open boundary control structure
2551 type(MOM_restart_CS), optional, intent(in) :: restart_CS !< MOM restart control structure
2552
2553 ! Local variables
2554 type(OBC_segment_type), pointer :: segment => NULL()
2555 real :: I_scale ! The inverse of the scaling factor for the tracers.
2556 ! For salinity the units would be [ppt S-1 ~> 1]
2557 logical :: set_tres_x, set_tres_y
2558 character(len=12) :: x_var_name, y_var_name
2559 integer :: i, j, k, m, n
2560
25610 do m=1,OBC%ntr
2562
25630 set_tres_x = allocated(OBC%tres_x) .and. OBC%tracer_x_reservoirs_used(m)
25640 set_tres_y = allocated(OBC%tres_y) .and. OBC%tracer_y_reservoirs_used(m)
2565
25660 if (present(restart_CS)) then
2567 ! Set the names of the reservoirs for this tracer in the restart file, and inquire whether
2568 ! they have been initialized
25690 if (modulo(G%HI%turns, 2) == 0) then
25700 write(x_var_name,'("tres_x_",I3.3)') m
25710 write(y_var_name,'("tres_y_",I3.3)') m
2572 else
25730 write(x_var_name,'("tres_y_",I3.3)') m
25740 write(y_var_name,'("tres_x_",I3.3)') m
2575 endif
25760 if (set_tres_x) set_tres_x = .not.query_initialized(OBC%tres_x, x_var_name, restart_CS)
25770 if (set_tres_y) set_tres_y = .not.query_initialized(OBC%tres_y, y_var_name, restart_CS)
2578 endif
2579
25800 do n=1,OBC%number_of_segments
25810 segment => OBC%segment(n)
25820 if (associated(segment%tr_Reg)) then ; if (allocated(segment%tr_Reg%Tr(m)%tres)) then
25830 I_scale = 1.0 ; if (segment%tr_Reg%Tr(m)%scale /= 0.0) I_scale = 1.0 / segment%tr_Reg%Tr(m)%scale
2584
25850 if (segment%is_E_or_W .and. set_tres_x) then
25860 I = segment%HI%IsdB
25870 if (segment%tr_Reg%Tr(m)%is_initialized) then
25880 do k=1,GV%ke ; do j=segment%HI%jsd,segment%HI%jed
25890 OBC%tres_x(I,j,k,m) = I_scale * segment%tr_Reg%Tr(m)%tres(i,j,k)
2590 enddo ; enddo
2591 else
25920 do k=1,GV%ke ; do j=segment%HI%jsd,segment%HI%jed
25930 OBC%tres_x(I,j,k,m) = I_scale * segment%tr_Reg%Tr(m)%t(i,j,k)
2594 enddo ; enddo
2595 endif
25960 elseif (segment%is_N_or_S .and. set_tres_y) then
25970 J = segment%HI%JsdB
25980 if (segment%tr_Reg%Tr(m)%is_initialized) then
25990 do k=1,GV%ke ; do i=segment%HI%isd,segment%HI%ied
26000 OBC%tres_y(i,J,k,m) = I_scale * segment%tr_Reg%Tr(m)%tres(i,J,k)
2601 enddo ; enddo
2602 else
26030 do k=1,GV%ke ; do i=segment%HI%isd,segment%HI%ied
26040 OBC%tres_y(i,J,k,m) = I_scale * segment%tr_Reg%Tr(m)%t(i,J,k)
2605 enddo ; enddo
2606 endif
2607 endif
2608 endif ; endif
2609 enddo
2610 enddo
2611
26120end subroutine setup_OBC_tracer_reservoirs
2613
2614!> Initialize the thickness reservoirs values, perhaps only if they have not been set via a restart file.
26150subroutine setup_OBC_thickness_reservoirs(G, GV, OBC, restart_CS)
2616 type(ocean_grid_type), intent(in) :: G !< Ocean grid structure
2617 type(verticalGrid_type), intent(in) :: GV !< The ocean's vertical grid structure
2618 type(ocean_OBC_type), target, intent(inout) :: OBC !< Open boundary control structure
2619 type(MOM_restart_CS), optional, intent(in) :: restart_CS !< MOM restart control structure
2620
2621 ! Local variables
2622 type(OBC_segment_type), pointer :: segment => NULL()
2623 real :: I_scale ! The inverse of the scaling factor for the thicknesses.
2624 ! [m Z-1 ~> 1]
2625 logical :: set_h_res_x, set_h_res_y
2626 character(len=12) :: x_var_name, y_var_name
2627 integer :: i, j, k, n
2628
26290 set_h_res_x = allocated(OBC%h_res_x) .and. OBC%thickness_x_reservoirs_used
26300 set_h_res_y = allocated(OBC%h_res_y) .and. OBC%thickness_y_reservoirs_used
2631
26320 if (present(restart_CS)) then
2633 ! Set the names of the reservoirs for the layer thickness in the restart file, and inquire
2634 ! whether they have been initialized
26350 if (modulo(G%HI%turns, 2) == 0) then
26360 write(x_var_name,'("h_res_x")')
26370 write(y_var_name,'("h_res_y")')
2638 else
26390 write(x_var_name,'("h_res_y")')
26400 write(y_var_name,'("h_res_x")')
2641 endif
26420 if (set_h_res_x) set_h_res_x = .not.query_initialized(OBC%h_res_x, x_var_name, restart_CS)
26430 if (set_h_res_y) set_h_res_y = .not.query_initialized(OBC%h_res_y, y_var_name, restart_CS)
2644 endif
2645
26460 do n=1,OBC%number_of_segments
26470 segment => OBC%segment(n)
26480 if (associated(segment%h_Reg)) then ; if (allocated(segment%h_Reg%h_res)) then
26490 I_scale = 1.0 ; if (segment%h_Reg%scale /= 0.0) I_scale = 1.0 / segment%h_Reg%scale
2650
26510 if (segment%is_E_or_W .and. set_h_res_x) then
26520 I = segment%HI%IsdB
26530 if (segment%h_Reg%is_initialized) then
26540 do k=1,GV%ke ; do j=segment%HI%jsd,segment%HI%jed
26550 OBC%h_res_x(I,j,k) = I_scale * segment%h_Reg%h_res(i,j,k)
2656 enddo ; enddo
2657 else
26580 do k=1,GV%ke ; do j=segment%HI%jsd,segment%HI%jed
26590 OBC%h_res_x(I,j,k) = I_scale * segment%h_Reg%h(i,j,k)
2660 enddo ; enddo
2661 endif
26620 elseif (segment%is_N_or_S .and. set_h_res_y) then
26630 J = segment%HI%JsdB
26640 if (segment%h_Reg%is_initialized) then
26650 do k=1,GV%ke ; do i=segment%HI%isd,segment%HI%ied
26660 OBC%h_res_y(i,J,k) = I_scale * segment%h_Reg%h_res(i,J,k)
2667 enddo ; enddo
2668 else
26690 do k=1,GV%ke ; do i=segment%HI%isd,segment%HI%ied
26700 OBC%h_res_y(i,J,k) = I_scale * segment%h_Reg%h(i,J,k)
2671 enddo ; enddo
2672 endif
2673 endif
2674 endif ; endif
2675 enddo
2676
26770end subroutine setup_OBC_thickness_reservoirs
2678
2679!> Record that the tracer reservoirs have been initialized so that their values are not reset later.
26800subroutine set_initialized_OBC_tracer_reservoirs(G, OBC, restart_CS)
2681 type(ocean_grid_type), intent(in) :: G !< Ocean grid structure
2682 type(ocean_OBC_type), intent(in) :: OBC !< Open boundary control structure
2683 type(MOM_restart_CS), intent(inout) :: restart_CS !< MOM restart control structure
2684 character(len=12) :: x_var_name, y_var_name
2685 integer :: m
2686
26870 do m=1,OBC%ntr
2688 ! Set the names of the reservoirs for this tracer in the restart file
26890 if (modulo(G%HI%turns, 2) == 0) then
26900 write(x_var_name,'("tres_x_",I3.3)') m
26910 write(y_var_name,'("tres_y_",I3.3)') m
2692 else
26930 write(x_var_name,'("tres_y_",I3.3)') m
26940 write(y_var_name,'("tres_x_",I3.3)') m
2695 endif
2696
26970 if (OBC%tracer_x_reservoirs_used(m)) call set_initialized(OBC%tres_x, x_var_name, restart_CS)
26980 if (OBC%tracer_y_reservoirs_used(m)) call set_initialized(OBC%tres_y, y_var_name, restart_CS)
2699 enddo
2700
27010end subroutine set_initialized_OBC_tracer_reservoirs
2702
2703!> Fill segment%h_Reg from restart fields.
27040subroutine copy_thickness_reservoirs(OBC, G, GV)
2705 type(ocean_grid_type), intent(inout) :: G !< Ocean grid structure
2706 type(verticalGrid_type), intent(in) :: GV !< The ocean's vertical grid structure
2707 type(ocean_OBC_type), pointer :: OBC !< Open boundary control structure
2708 ! Local variables
2709 type(OBC_segment_type), pointer :: segment => NULL()
2710 integer :: i, j, k, n
2711 logical :: sym
2712
27130 if (.not.associated(OBC)) return
2714
27150 if (.not.(OBC%thickness_x_reservoirs_used .or. OBC%thickness_y_reservoirs_used)) &
27160 return
2717
2718 ! Now thickness reservoirs
27190 do n=1,OBC%number_of_segments
27200 segment=>OBC%segment(n)
27210 if (associated(segment%h_Reg)) then
27220 if (segment%is_E_or_W) then
27230 I = segment%HI%IsdB
27240 if (allocated(segment%h_Reg%h_res)) then
27250 do k=1,GV%ke
27260 do j=segment%HI%jsd,segment%HI%jed
27270 segment%h_Reg%h_res(I,j,k) = segment%h_Reg%scale * OBC%h_res_x(I,j,k)
2728 enddo
2729 enddo
2730 endif
2731 else
27320 J = segment%HI%JsdB
27330 if (allocated(segment%h_Reg%h_res)) then
27340 do k=1,GV%ke
27350 do i=segment%HI%isd,segment%HI%ied
27360 segment%h_Reg%h_res(i,J,k) = segment%h_Reg%scale * OBC%h_res_y(i,J,k)
2737 enddo
2738 enddo
2739 endif
2740 endif
2741 endif
2742 enddo
2743
27440 if (OBC%debug) then
27450 sym = G%Domain%symmetric
27460 if (allocated(OBC%h_res_x) .and. allocated(OBC%h_res_y)) then
2747 call uvchksum("radiation_OBCs: OBC%h_res_[xy]", OBC%h_res_x(:,:,:), OBC%h_res_y(:,:,:), G%HI, &
27480 haloshift=0, symmetric=sym, scalar_pair=.true., unscale=1.0)
2749 endif
2750 endif
2751
2752end subroutine copy_thickness_reservoirs
2753
2754!> Apply radiation conditions to 3D u,v at open boundaries
27550subroutine radiation_open_bdry_conds(OBC, u_new, u_old, v_new, v_old, G, GV, US, dt)
2756 type(ocean_grid_type), intent(inout) :: G !< Ocean grid structure
2757 type(verticalGrid_type), intent(in) :: GV !< The ocean's vertical grid structure
2758 type(ocean_OBC_type), pointer :: OBC !< Open boundary control structure
2759 real, dimension(SZIB_(G),SZJ_(G),SZK_(GV)), intent(inout) :: u_new !< On exit, new u values on open boundaries
2760 !! On entry, the old time-level u but including
2761 !! barotropic accelerations [L T-1 ~> m s-1].
2762 real, dimension(SZIB_(G),SZJ_(G),SZK_(GV)), intent(in) :: u_old !< Original unadjusted u [L T-1 ~> m s-1]
2763 real, dimension(SZI_(G),SZJB_(G),SZK_(GV)), intent(inout) :: v_new !< On exit, new v values on open boundaries.
2764 !! On entry, the old time-level v but including
2765 !! barotropic accelerations [L T-1 ~> m s-1].
2766 real, dimension(SZI_(G),SZJB_(G),SZK_(GV)), intent(in) :: v_old !< Original unadjusted v [L T-1 ~> m s-1]
2767 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
2768 real, intent(in) :: dt !< Appropriate timestep [T ~> s]
2769 ! Local variables
2770 real :: dhdt, dhdx, dhdy ! One-point differences in time or space [L T-1 ~> m s-1]
2771 real :: gamma_u, gamma_2 ! Fractional weightings of new values [nondim]
2772 real :: tau ! A local nudging timescale [T ~> s]
2773 real :: rx_max, ry_max ! coefficients for radiation [nondim]
2774 real :: rx_new, rx_avg ! coefficients for radiation [nondim] or [L2 T-2 ~> m2 s-2]
2775 real :: ry_new, ry_avg ! coefficients for radiation [nondim] or [L2 T-2 ~> m2 s-2]
2776 real :: cff_new, cff_avg ! denominator in oblique [L2 T-2 ~> m2 s-2]
2777 real, allocatable, dimension(:,:,:) :: &
27780 rx_tang_rad, & ! The phase speed at u-points for tangential oblique OBCs
2779 ! in units of grid points per timestep [nondim],
2780 ! discretized at the corner (PV) points.
27810 ry_tang_rad, & ! The phase speed at v-points for tangential oblique OBCs
2782 ! in units of grid points per timestep [nondim],
2783 ! discretized at the corner (PV) points.
27840 rx_tang_obl, & ! The x-coefficient for tangential oblique OBCs [L2 T-2 ~> m2 s-2],
2785 ! discretized at the corner (PV) points.
27860 ry_tang_obl, & ! The y-coefficient for tangential oblique OBCs [L2 T-2 ~> m2 s-2],
2787 ! discretized at the corner (PV) points.
27880 cff_tangential ! The denominator for tangential oblique OBCs [L2 T-2 ~> m2 s-2],
2789 ! discretized at the corner (PV) points.
2790 real :: eps ! A small velocity squared [L2 T-2 ~> m2 s-2]
2791 type(OBC_segment_type), pointer :: segment => NULL()
2792 integer :: i, j, k, is, ie, js, je, m, nz, n
2793 integer :: is_obc, ie_obc, js_obc, je_obc
2794 logical :: sym
2795 character(len=3) :: var_num
2796
27970 is = G%isc ; ie = G%iec ; js = G%jsc ; je = G%jec ; nz = GV%ke
2798
27990 if (.not.associated(OBC)) return
2800
28010 if (.not.(OBC%open_u_BCs_exist_globally .or. OBC%open_v_BCs_exist_globally)) &
28020 return
2803
28040 if (OBC%debug) call chksum_OBC_segments(OBC, G, GV, US, OBC%nk_OBC_debug)
2805
28060 eps = 1.0e-20*US%m_s_to_L_T**2
2807
2808 !! Copy previously calculated phase velocity from global arrays into segments
2809 !! This is terribly inefficient and temporary solution for continuity across restarts
2810 !! and needs to be revisited in the future.
28110 if (OBC%gamma_uv < 1.0) then
28120 do n=1,OBC%number_of_segments
28130 segment => OBC%segment(n)
28140 if (.not. segment%on_pe) cycle
28150 if (segment%is_E_or_W .and. segment%radiation) then
28160 do k=1,GV%ke
28170 I=segment%HI%IsdB
28180 do j=segment%HI%jsd,segment%HI%jed
28190 segment%rx_norm_rad(I,j,k) = OBC%rx_normal(I,j,k)
2820 enddo
2821 enddo
28220 elseif (segment%is_N_or_S .and. segment%radiation) then
28230 do k=1,GV%ke
28240 J=segment%HI%JsdB
28250 do i=segment%HI%isd,segment%HI%ied
28260 segment%ry_norm_rad(i,J,k) = OBC%ry_normal(i,J,k)
2827 enddo
2828 enddo
2829 endif
28300 if (segment%is_E_or_W .and. segment%oblique) then
28310 do k=1,GV%ke
28320 I=segment%HI%IsdB
28330 do j=segment%HI%jsd,segment%HI%jed
28340 segment%rx_norm_obl(I,j,k) = OBC%rx_oblique_u(I,j,k)
28350 segment%ry_norm_obl(I,j,k) = OBC%ry_oblique_u(I,j,k)
28360 segment%cff_normal(I,j,k) = OBC%cff_normal_u(I,j,k)
2837 enddo
2838 enddo
28390 elseif (segment%is_N_or_S .and. segment%oblique) then
28400 do k=1,GV%ke
28410 J=segment%HI%JsdB
28420 do i=segment%HI%isd,segment%HI%ied
28430 segment%rx_norm_obl(i,J,k) = OBC%rx_oblique_v(i,J,k)
28440 segment%ry_norm_obl(i,J,k) = OBC%ry_oblique_v(i,J,k)
28450 segment%cff_normal(i,J,k) = OBC%cff_normal_v(i,J,k)
2846 enddo
2847 enddo
2848 endif
2849 enddo
2850 endif
2851
2852 ! Now tracers (if any)
28530 do n=1,OBC%number_of_segments
28540 segment => OBC%segment(n)
28550 if (associated(segment%tr_Reg)) then
28560 if (segment%is_E_or_W) then
28570 I = segment%HI%IsdB
28580 do m=1,OBC%ntr
28590 if (allocated(segment%tr_Reg%Tr(m)%tres)) then
28600 do k=1,GV%ke
28610 do j=segment%HI%jsd,segment%HI%jed
28620 segment%tr_Reg%Tr(m)%tres(I,j,k) = segment%tr_Reg%Tr(m)%scale * OBC%tres_x(I,j,k,m)
2863 enddo
2864 enddo
2865 endif
2866 enddo
2867 else
28680 J = segment%HI%JsdB
28690 do m=1,OBC%ntr
28700 if (allocated(segment%tr_Reg%Tr(m)%tres)) then
28710 do k=1,GV%ke
28720 do i=segment%HI%isd,segment%HI%ied
28730 segment%tr_Reg%Tr(m)%tres(i,J,k) = segment%tr_Reg%Tr(m)%scale * OBC%tres_y(i,J,k,m)
2874 enddo
2875 enddo
2876 endif
2877 enddo
2878 endif
2879 endif
2880 enddo
2881
28820 gamma_u = OBC%gamma_uv
28830 rx_max = OBC%rx_max ; ry_max = OBC%rx_max
28840 do n=1,OBC%number_of_segments
28850 segment => OBC%segment(n)
28860 if (.not. segment%on_pe) cycle
28870 if (segment%oblique) call gradient_at_q_points(G, GV, segment, u_new(:,:,:), v_new(:,:,:))
28880 if (segment%direction == OBC_DIRECTION_E) then
28890 I=segment%HI%IsdB
28900 if (I<G%HI%IscB) cycle
28910 do k=1,nz ; do j=segment%HI%jsd,segment%HI%jed
28920 if (segment%radiation) then
28930 dhdt = (u_old(I-1,j,k) - u_new(I-1,j,k)) !old-new
28940 dhdx = (u_new(I-1,j,k) - u_new(I-2,j,k)) !in new time backward sashay for I-1
28950 rx_new = 0.0
28960 if (dhdt*dhdx > 0.0) rx_new = min( (dhdt/dhdx), rx_max) ! outward phase speed
28970 if (gamma_u < 1.0) then
28980 rx_avg = (1.0-gamma_u)*segment%rx_norm_rad(I,j,k) + gamma_u*rx_new
2899 else
29000 rx_avg = rx_new
2901 endif
29020 segment%rx_norm_rad(I,j,k) = rx_avg
2903 ! The new boundary value is interpolated between future interior
2904 ! value, u_new(I-1) and past boundary value but with barotropic
2905 ! accelerations, u_new(I).
29060 segment%normal_vel(I,j,k) = (u_new(I,j,k) + rx_avg*u_new(I-1,j,k)) / (1.0+rx_avg)
2907 ! Copy restart fields into 3-d arrays. This is an inefficient and temporary issues
2908 ! implemented as a work-around to limitations in restart capability
29090 if (gamma_u < 1.0) then
29100 OBC%rx_normal(I,j,k) = segment%rx_norm_rad(I,j,k)
2911 endif
29120 elseif (segment%oblique) then
29130 dhdt = (u_old(I-1,j,k) - u_new(I-1,j,k)) !old-new
29140 dhdx = (u_new(I-1,j,k) - u_new(I-2,j,k)) !in new time backward sashay for I-1
29150 if (dhdt*(segment%grad_normal(J,1,k) + segment%grad_normal(J-1,1,k)) > 0.0) then
29160 dhdy = segment%grad_normal(J-1,1,k)
29170 elseif (dhdt*(segment%grad_normal(J,1,k) + segment%grad_normal(J-1,1,k)) == 0.0) then
29180 dhdy = 0.0
2919 else
29200 dhdy = segment%grad_normal(J,1,k)
2921 endif
29220 if (dhdt*dhdx < 0.0) dhdt = 0.0
29230 cff_new = max((dhdx*dhdx) + (dhdy*dhdy), eps)
29240 rx_new = min(dhdt*dhdx, cff_new*rx_max)
29250 ry_new = min(cff_new,max(dhdt*dhdy,-cff_new))
29260 if (gamma_u < 1.0) then
29270 rx_avg = (1.0-gamma_u)*segment%rx_norm_obl(I,j,k) + gamma_u*rx_new
29280 ry_avg = (1.0-gamma_u)*segment%ry_norm_obl(I,j,k) + gamma_u*ry_new
29290 cff_avg = (1.0-gamma_u)*segment%cff_normal(I,j,k) + gamma_u*cff_new
2930 else
29310 rx_avg = rx_new
29320 ry_avg = ry_new
29330 cff_avg = cff_new
2934 endif
29350 segment%rx_norm_obl(I,j,k) = rx_avg
29360 segment%ry_norm_obl(I,j,k) = ry_avg
29370 segment%cff_normal(I,j,k) = cff_avg
2938 segment%normal_vel(I,j,k) = ((cff_avg*u_new(I,j,k) + rx_avg*u_new(I-1,j,k)) - &
2939 (max(ry_avg,0.0)*segment%grad_normal(J-1,2,k) + &
2940 min(ry_avg,0.0)*segment%grad_normal(J,2,k))) / &
29410 (cff_avg + rx_avg)
29420 if (gamma_u < 1.0) then
2943 ! Copy restart fields into 3-d arrays. This is an inefficient and temporary
2944 ! implementation as a work-around to limitations in restart capability
29450 OBC%rx_oblique_u(I,j,k) = segment%rx_norm_obl(I,j,k)
29460 OBC%ry_oblique_u(I,j,k) = segment%ry_norm_obl(I,j,k)
29470 OBC%cff_normal_u(I,j,k) = segment%cff_normal(I,j,k)
2948 endif
29490 elseif (segment%gradient) then
29500 segment%normal_vel(I,j,k) = u_new(I-1,j,k)
2951 endif
29520 if ((segment%radiation .or. segment%oblique) .and. segment%nudged) then
2953 ! dhdt gets set to 0 on inflow in oblique case
29540 if (dhdt*dhdx <= 0.0) then
29550 tau = segment%Velocity_nudging_timescale_in
2956 else
29570 tau = segment%Velocity_nudging_timescale_out
2958 endif
29590 gamma_2 = dt / (tau + dt)
2960 segment%normal_vel(I,j,k) = (1.0 - gamma_2) * segment%normal_vel(I,j,k) + &
29610 gamma_2 * segment%nudged_normal_vel(I,j,k)
2962 endif
2963 enddo ; enddo
29640 if (segment%radiation_tan .or. segment%radiation_grad) then
29650 I=segment%HI%IsdB
29660 allocate(rx_tang_rad(segment%HI%IsdB:segment%HI%IedB,segment%HI%JsdB:segment%HI%JedB,nz))
29670 do k=1,nz
29680 if (gamma_u < 1.0) then
29690 rx_tang_rad(I,segment%HI%JsdB,k) = segment%rx_norm_rad(I,segment%HI%jsd,k)
29700 rx_tang_rad(I,segment%HI%JedB,k) = segment%rx_norm_rad(I,segment%HI%jed,k)
29710 do J=segment%HI%JsdB+1,segment%HI%JedB-1
29720 rx_tang_rad(I,J,k) = 0.5*(segment%rx_norm_rad(I,j,k) + segment%rx_norm_rad(I,j+1,k))
2973 enddo
2974 else
29750 do J=segment%HI%JsdB,segment%HI%JedB
29760 dhdt = v_old(i,J,k)-v_new(i,J,k) !old-new
29770 dhdx = v_new(i,J,k)-v_new(i-1,J,k) !in new time backward sashay for I-1
29780 rx_tang_rad(I,J,k) = 0.0
29790 if (dhdt*dhdx > 0.0) rx_tang_rad(I,J,k) = min( (dhdt/dhdx), rx_max) ! outward phase speed
2980 enddo
2981 endif
2982 enddo
29830 if (segment%radiation_tan) then
29840 do k=1,nz ; do J=segment%HI%JsdB,segment%HI%JedB
29850 rx_avg = rx_tang_rad(I,J,k)
29860 segment%tangential_vel(I,J,k) = (v_new(i,J,k) + rx_avg*v_new(i-1,J,k)) / (1.0+rx_avg)
2987 enddo ; enddo
2988 endif
29890 if (segment%nudged_tan) then
29900 do k=1,nz ; do J=segment%HI%JsdB,segment%HI%JedB
2991 ! dhdt gets set to 0 on inflow in oblique case
29920 if (rx_tang_rad(I,J,k) <= 0.0) then
29930 tau = segment%Velocity_nudging_timescale_in
2994 else
29950 tau = segment%Velocity_nudging_timescale_out
2996 endif
29970 gamma_2 = dt / (tau + dt)
2998 segment%tangential_vel(I,J,k) = (1.0 - gamma_2) * segment%tangential_vel(I,J,k) + &
29990 gamma_2 * segment%nudged_tangential_vel(I,J,k)
3000 enddo ; enddo
3001 endif
30020 if (segment%radiation_grad) then
30030 Js_obc = max(segment%HI%JsdB,G%jsd+1)
30040 Je_obc = min(segment%HI%JedB,G%jed-1)
30050 do k=1,nz ; do J=Js_obc,Je_obc
30060 rx_avg = rx_tang_rad(I,J,k)
3007! if (G%mask2dCu(I-1,j) > 0.0 .and. G%mask2dCu(I-1,j+1) > 0.0) then
3008! rx_avg = 0.5*(u_new(I-1,j,k) + u_new(I-1,j+1,k)) * dt * G%IdxBu(I-1,J)
3009! elseif (G%mask2dCu(I-1,j) > 0.0) then
3010! rx_avg = u_new(I-1,j,k) * dt * G%IdxBu(I-1,J)
3011! elseif (G%mask2dCu(I-1,j+1) > 0.0) then
3012! rx_avg = u_new(I-1,j+1,k) * dt * G%IdxBu(I-1,J)
3013! else
3014! rx_avg = 0.0
3015! endif
3016 segment%tangential_grad(I,J,k) = ((v_new(i,J,k) - v_new(i-1,J,k))*G%IdxBu(I-1,J) + &
30170 rx_avg*(v_new(i-1,J,k) - v_new(i-2,J,k))*G%IdxBu(I-2,J)) / (1.0+rx_avg)
3018 enddo ; enddo
3019 endif
30200 if (segment%nudged_grad) then
30210 do k=1,nz ; do J=segment%HI%JsdB,segment%HI%JedB
3022 ! dhdt gets set to 0 on inflow in oblique case
30230 if (rx_tang_rad(I,J,k) <= 0.0) then
30240 tau = segment%Velocity_nudging_timescale_in
3025 else
30260 tau = segment%Velocity_nudging_timescale_out
3027 endif
30280 gamma_2 = dt / (tau + dt)
3029 segment%tangential_grad(I,J,k) = (1.0 - gamma_2) * segment%tangential_grad(I,J,k) + &
30300 gamma_2 * segment%nudged_tangential_grad(I,J,k)
3031 enddo ; enddo
3032 endif
30330 deallocate(rx_tang_rad)
3034 endif
30350 if (segment%oblique_tan .or. segment%oblique_grad) then
30360 I=segment%HI%IsdB
30370 allocate(rx_tang_obl(segment%HI%IsdB:segment%HI%IedB,segment%HI%JsdB:segment%HI%JedB,nz))
30380 allocate(ry_tang_obl(segment%HI%IsdB:segment%HI%IedB,segment%HI%JsdB:segment%HI%JedB,nz))
30390 allocate(cff_tangential(segment%HI%IsdB:segment%HI%IedB,segment%HI%JsdB:segment%HI%JedB,nz))
30400 do k=1,nz
30410 if (gamma_u < 1.0) then
30420 rx_tang_obl(I,segment%HI%JsdB,k) = segment%rx_norm_obl(I,segment%HI%jsd,k)
30430 rx_tang_obl(I,segment%HI%JedB,k) = segment%rx_norm_obl(I,segment%HI%jed,k)
30440 ry_tang_obl(I,segment%HI%JsdB,k) = segment%ry_norm_obl(I,segment%HI%jsd,k)
30450 ry_tang_obl(I,segment%HI%JedB,k) = segment%ry_norm_obl(I,segment%HI%jed,k)
30460 cff_tangential(I,segment%HI%JsdB,k) = segment%cff_normal(I,segment%HI%jsd,k)
30470 cff_tangential(I,segment%HI%JedB,k) = segment%cff_normal(I,segment%HI%jed,k)
30480 do J=segment%HI%JsdB+1,segment%HI%JedB-1
30490 rx_tang_obl(I,J,k) = 0.5*(segment%rx_norm_obl(I,j,k) + segment%rx_norm_obl(I,j+1,k))
30500 ry_tang_obl(I,J,k) = 0.5*(segment%ry_norm_obl(I,j,k) + segment%ry_norm_obl(I,j+1,k))
30510 cff_tangential(I,J,k) = 0.5*(segment%cff_normal(I,j,k) + segment%cff_normal(I,j+1,k))
3052 enddo
3053 else
30540 do J=segment%HI%JsdB,segment%HI%JedB
30550 dhdt = v_old(i,J,k)-v_new(i,J,k) !old-new
30560 dhdx = v_new(i,J,k)-v_new(i-1,J,k) !in new time backward sashay for I-1
30570 if (dhdt*(segment%grad_tan(j,1,k) + segment%grad_tan(j+1,1,k)) > 0.0) then
30580 dhdy = segment%grad_tan(j,1,k)
30590 elseif (dhdt*(segment%grad_tan(j,1,k) + segment%grad_tan(j+1,1,k)) == 0.0) then
30600 dhdy = 0.0
3061 else
30620 dhdy = segment%grad_tan(j+1,1,k)
3063 endif
30640 if (dhdt*dhdx < 0.0) dhdt = 0.0
30650 cff_new = max((dhdx*dhdx) + (dhdy*dhdy), eps)
30660 rx_new = min(dhdt*dhdx, cff_new*rx_max)
30670 ry_new = min(cff_new,max(dhdt*dhdy,-cff_new))
30680 rx_tang_obl(I,J,k) = rx_new
30690 ry_tang_obl(I,J,k) = ry_new
30700 cff_tangential(I,J,k) = cff_new
3071 enddo
3072 endif
3073 enddo
30740 if (segment%oblique_tan) then
30750 do k=1,nz ; do J=segment%HI%JsdB,segment%HI%JedB
30760 rx_avg = rx_tang_obl(I,J,k)
30770 ry_avg = ry_tang_obl(I,J,k)
30780 cff_avg = cff_tangential(I,J,k)
3079 segment%tangential_vel(I,J,k) = ((cff_avg*v_new(i,J,k) + rx_avg*v_new(i-1,J,k)) - &
3080 (max(ry_avg,0.0)*segment%grad_tan(j,2,k) + &
3081 min(ry_avg,0.0)*segment%grad_tan(j+1,2,k))) / &
30820 (cff_avg + rx_avg)
3083 enddo ; enddo
3084 endif
30850 if (segment%nudged_tan) then
30860 do k=1,nz ; do J=segment%HI%JsdB,segment%HI%JedB
3087 ! dhdt gets set to 0 on inflow in oblique case
30880 if (rx_tang_obl(I,J,k) <= 0.0) then
30890 tau = segment%Velocity_nudging_timescale_in
3090 else
30910 tau = segment%Velocity_nudging_timescale_out
3092 endif
30930 gamma_2 = dt / (tau + dt)
3094 segment%tangential_vel(I,J,k) = (1.0 - gamma_2) * segment%tangential_vel(I,J,k) + &
30950 gamma_2 * segment%nudged_tangential_vel(I,J,k)
3096 enddo ; enddo
3097 endif
30980 if (segment%oblique_grad) then
30990 Js_obc = max(segment%HI%JsdB,G%jsd+1)
31000 Je_obc = min(segment%HI%JedB,G%jed-1)
31010 do k=1,nz ; do J=segment%HI%JsdB+1,segment%HI%JedB-1
31020 rx_avg = rx_tang_obl(I,J,k)
31030 ry_avg = ry_tang_obl(I,J,k)
31040 cff_avg = cff_tangential(I,J,k)
3105 segment%tangential_grad(I,J,k) = &
3106 ((cff_avg*(v_new(i,J,k) - v_new(i-1,J,k))*G%IdxBu(I-1,J) + &
3107 rx_avg*(v_new(i-1,J,k) - v_new(i-2,J,k))*G%IdxBu(I-2,J)) - &
3108 (max(ry_avg,0.0)*segment%grad_gradient(J,2,k) + &
3109 min(ry_avg,0.0)*segment%grad_gradient(J+1,2,k)) ) / &
31100 (cff_avg + rx_avg)
3111 enddo ; enddo
3112 endif
31130 if (segment%nudged_grad) then
31140 do k=1,nz ; do J=segment%HI%JsdB,segment%HI%JedB
3115 ! dhdt gets set to 0 on inflow in oblique case
31160 if (rx_tang_obl(I,J,k) <= 0.0) then
31170 tau = segment%Velocity_nudging_timescale_in
3118 else
31190 tau = segment%Velocity_nudging_timescale_out
3120 endif
31210 gamma_2 = dt / (tau + dt)
3122 segment%tangential_grad(I,J,k) = (1.0 - gamma_2) * segment%tangential_grad(I,J,k) + &
31230 gamma_2 * segment%nudged_tangential_grad(I,J,k)
3124 enddo ; enddo
3125 endif
31260 deallocate(rx_tang_obl)
31270 deallocate(ry_tang_obl)
31280 deallocate(cff_tangential)
3129 endif
3130 endif
3131
31320 if (segment%direction == OBC_DIRECTION_W) then
31330 I=segment%HI%IsdB
31340 if (I>G%HI%IecB) cycle
31350 do k=1,nz ; do j=segment%HI%jsd,segment%HI%jed
31360 if (segment%radiation) then
31370 dhdt = (u_old(I+1,j,k) - u_new(I+1,j,k)) !old-new
31380 dhdx = (u_new(I+1,j,k) - u_new(I+2,j,k)) !in new time forward sashay for I+1
31390 rx_new = 0.0
31400 if (dhdt*dhdx > 0.0) rx_new = min( (dhdt/dhdx), rx_max)
31410 if (gamma_u < 1.0) then
31420 rx_avg = (1.0-gamma_u)*segment%rx_norm_rad(I,j,k) + gamma_u*rx_new
3143 else
31440 rx_avg = rx_new
3145 endif
31460 segment%rx_norm_rad(I,j,k) = rx_avg
3147 ! The new boundary value is interpolated between future interior
3148 ! value, u_new(I+1) and past boundary value but with barotropic
3149 ! accelerations, u_new(I).
31500 segment%normal_vel(I,j,k) = (u_new(I,j,k) + rx_avg*u_new(I+1,j,k)) / (1.0+rx_avg)
31510 if (gamma_u < 1.0) then
3152 ! Copy restart fields into 3-d arrays. This is an inefficient and temporary issues
3153 ! implemented as a work-around to limitations in restart capability
31540 OBC%rx_normal(I,j,k) = segment%rx_norm_rad(I,j,k)
3155 endif
31560 elseif (segment%oblique) then
31570 dhdt = (u_old(I+1,j,k) - u_new(I+1,j,k)) !old-new
31580 dhdx = (u_new(I+1,j,k) - u_new(I+2,j,k)) !in new time forward sashay for I+1
31590 if (dhdt*(segment%grad_normal(J,1,k) + segment%grad_normal(J-1,1,k)) > 0.0) then
31600 dhdy = segment%grad_normal(J-1,1,k)
31610 elseif (dhdt*(segment%grad_normal(J,1,k) + segment%grad_normal(J-1,1,k)) == 0.0) then
31620 dhdy = 0.0
3163 else
31640 dhdy = segment%grad_normal(J,1,k)
3165 endif
31660 if (dhdt*dhdx < 0.0) dhdt = 0.0
3167
31680 cff_new = max((dhdx*dhdx) + (dhdy*dhdy), eps)
31690 rx_new = min(dhdt*dhdx, cff_new*rx_max)
31700 ry_new = min(cff_new,max(dhdt*dhdy,-cff_new))
31710 if (gamma_u < 1.0) then
31720 rx_avg = (1.0-gamma_u)*segment%rx_norm_obl(I,j,k) + gamma_u*rx_new
31730 ry_avg = (1.0-gamma_u)*segment%ry_norm_obl(I,j,k) + gamma_u*ry_new
31740 cff_avg = (1.0-gamma_u)*segment%cff_normal(I,j,k) + gamma_u*cff_new
3175 else
31760 rx_avg = rx_new
31770 ry_avg = ry_new
31780 cff_avg = cff_new
3179 endif
31800 segment%rx_norm_obl(I,j,k) = rx_avg
31810 segment%ry_norm_obl(I,j,k) = ry_avg
31820 segment%cff_normal(I,j,k) = cff_avg
3183 segment%normal_vel(I,j,k) = ((cff_avg*u_new(I,j,k) + rx_avg*u_new(I+1,j,k)) - &
3184 (max(ry_avg,0.0)*segment%grad_normal(J-1,2,k) + &
3185 min(ry_avg,0.0)*segment%grad_normal(J,2,k))) / &
31860 (cff_avg + rx_avg)
31870 if (gamma_u < 1.0) then
3188 ! Copy restart fields into 3-d arrays. This is an inefficient and temporary issues
3189 ! implemented as a work-around to limitations in restart capability
31900 OBC%rx_oblique_u(I,j,k) = segment%rx_norm_obl(I,j,k)
31910 OBC%ry_oblique_u(I,j,k) = segment%ry_norm_obl(I,j,k)
31920 OBC%cff_normal_u(I,j,k) = segment%cff_normal(I,j,k)
3193 endif
31940 elseif (segment%gradient) then
31950 segment%normal_vel(I,j,k) = u_new(I+1,j,k)
3196 endif
31970 if ((segment%radiation .or. segment%oblique) .and. segment%nudged) then
3198 ! dhdt gets set to 0. on inflow in oblique case
31990 if (dhdt*dhdx <= 0.0) then
32000 tau = segment%Velocity_nudging_timescale_in
3201 else
32020 tau = segment%Velocity_nudging_timescale_out
3203 endif
32040 gamma_2 = dt / (tau + dt)
3205 segment%normal_vel(I,j,k) = (1.0 - gamma_2) * segment%normal_vel(I,j,k) + &
32060 gamma_2 * segment%nudged_normal_vel(I,j,k)
3207 endif
3208 enddo ; enddo
32090 if (segment%radiation_tan .or. segment%radiation_grad) then
32100 I=segment%HI%IsdB
32110 allocate(rx_tang_rad(segment%HI%IsdB:segment%HI%IedB,segment%HI%JsdB:segment%HI%JedB,nz))
32120 do k=1,nz
32130 if (gamma_u < 1.0) then
32140 rx_tang_rad(I,segment%HI%JsdB,k) = segment%rx_norm_rad(I,segment%HI%jsd,k)
32150 rx_tang_rad(I,segment%HI%JedB,k) = segment%rx_norm_rad(I,segment%HI%jed,k)
32160 do J=segment%HI%JsdB+1,segment%HI%JedB-1
32170 rx_tang_rad(I,J,k) = 0.5*(segment%rx_norm_rad(I,j,k) + segment%rx_norm_rad(I,j+1,k))
3218 enddo
3219 else
32200 do J=segment%HI%JsdB,segment%HI%JedB
32210 dhdt = v_old(i+1,J,k)-v_new(i+1,J,k) !old-new
32220 dhdx = v_new(i+1,J,k)-v_new(i+2,J,k) !in new time backward sashay for I-1
32230 rx_tang_rad(I,J,k) = 0.0
32240 if (dhdt*dhdx > 0.0) rx_tang_rad(I,J,k) = min( (dhdt/dhdx), rx_max) ! outward phase speed
3225 enddo
3226 endif
3227 enddo
32280 if (segment%radiation_tan) then
32290 do k=1,nz ; do J=segment%HI%JsdB,segment%HI%JedB
32300 rx_avg = rx_tang_rad(I,J,k)
32310 segment%tangential_vel(I,J,k) = (v_new(i+1,J,k) + rx_avg*v_new(i+2,J,k)) / (1.0+rx_avg)
3232 enddo ; enddo
3233 endif
32340 if (segment%nudged_tan) then
32350 do k=1,nz ; do J=segment%HI%JsdB,segment%HI%JedB
3236 ! dhdt gets set to 0 on inflow in oblique case
32370 if (rx_tang_rad(I,J,k) <= 0.0) then
32380 tau = segment%Velocity_nudging_timescale_in
3239 else
32400 tau = segment%Velocity_nudging_timescale_out
3241 endif
32420 gamma_2 = dt / (tau + dt)
3243 segment%tangential_vel(I,J,k) = (1.0 - gamma_2) * segment%tangential_vel(I,J,k) + &
32440 gamma_2 * segment%nudged_tangential_vel(I,J,k)
3245 enddo ; enddo
3246 endif
32470 if (segment%radiation_grad) then
32480 Js_obc = max(segment%HI%JsdB,G%jsd+1)
32490 Je_obc = min(segment%HI%JedB,G%jed-1)
32500 do k=1,nz ; do J=Js_obc,Je_obc
32510 rx_avg = rx_tang_rad(I,J,k)
3252! if (G%mask2dCu(I+1,j) > 0.0 .and. G%mask2dCu(I+1,j+1) > 0.0) then
3253! rx_avg = 0.5*(u_new(I+1,j,k) + u_new(I+1,j+1,k)) * dt * G%IdxBu(I+1,J)
3254! elseif (G%mask2dCu(I+1,j) > 0.0) then
3255! rx_avg = u_new(I+1,j,k) * dt * G%IdxBu(I+1,J)
3256! elseif (G%mask2dCu(I+1,j+1) > 0.0) then
3257! rx_avg = u_new(I+1,j+1,k) * dt * G%IdxBu(I+1,J)
3258! else
3259! rx_avg = 0.0
3260! endif
3261 segment%tangential_grad(I,J,k) = ((v_new(i+2,J,k) - v_new(i+1,J,k))*G%IdxBu(I+1,J) + &
32620 rx_avg*(v_new(i+3,J,k) - v_new(i+2,J,k))*G%IdxBu(I+2,J)) / (1.0+rx_avg)
3263 enddo ; enddo
3264 endif
32650 if (segment%nudged_grad) then
32660 do k=1,nz ; do J=segment%HI%JsdB,segment%HI%JedB
3267 ! dhdt gets set to 0 on inflow in oblique case
32680 if (rx_tang_rad(I,J,k) <= 0.0) then
32690 tau = segment%Velocity_nudging_timescale_in
3270 else
32710 tau = segment%Velocity_nudging_timescale_out
3272 endif
32730 gamma_2 = dt / (tau + dt)
3274 segment%tangential_grad(I,J,k) = (1.0 - gamma_2) * segment%tangential_grad(I,J,k) + &
32750 gamma_2 * segment%nudged_tangential_grad(I,J,k)
3276 enddo ; enddo
3277 endif
32780 deallocate(rx_tang_rad)
3279 endif
32800 if (segment%oblique_tan .or. segment%oblique_grad) then
32810 I=segment%HI%IsdB
32820 allocate(rx_tang_obl(segment%HI%IsdB:segment%HI%IedB,segment%HI%JsdB:segment%HI%JedB,nz))
32830 allocate(ry_tang_obl(segment%HI%IsdB:segment%HI%IedB,segment%HI%JsdB:segment%HI%JedB,nz))
32840 allocate(cff_tangential(segment%HI%IsdB:segment%HI%IedB,segment%HI%JsdB:segment%HI%JedB,nz))
32850 do k=1,nz
32860 if (gamma_u < 1.0) then
32870 rx_tang_obl(I,segment%HI%JsdB,k) = segment%rx_norm_obl(I,segment%HI%jsd,k)
32880 rx_tang_obl(I,segment%HI%JedB,k) = segment%rx_norm_obl(I,segment%HI%jed,k)
32890 ry_tang_obl(I,segment%HI%JsdB,k) = segment%ry_norm_obl(I,segment%HI%jsd,k)
32900 ry_tang_obl(I,segment%HI%JedB,k) = segment%ry_norm_obl(I,segment%HI%jed,k)
32910 cff_tangential(I,segment%HI%JsdB,k) = segment%cff_normal(I,segment%HI%jsd,k)
32920 cff_tangential(I,segment%HI%JedB,k) = segment%cff_normal(I,segment%HI%jed,k)
32930 do J=segment%HI%JsdB+1,segment%HI%JedB-1
32940 rx_tang_obl(I,J,k) = 0.5*(segment%rx_norm_obl(I,j,k) + segment%rx_norm_obl(I,j+1,k))
32950 ry_tang_obl(I,J,k) = 0.5*(segment%ry_norm_obl(I,j,k) + segment%ry_norm_obl(I,j+1,k))
32960 cff_tangential(I,J,k) = 0.5*(segment%cff_normal(I,j,k) + segment%cff_normal(I,j+1,k))
3297 enddo
3298 else
32990 do J=segment%HI%JsdB,segment%HI%JedB
33000 dhdt = v_old(i+1,J,k)-v_new(i+1,J,k) !old-new
33010 dhdx = v_new(i+1,J,k)-v_new(i+2,J,k) !in new time backward sashay for I-1
33020 if (dhdt*(segment%grad_tan(j,1,k) + segment%grad_tan(j+1,1,k)) > 0.0) then
33030 dhdy = segment%grad_tan(j,1,k)
33040 elseif (dhdt*(segment%grad_tan(j,1,k) + segment%grad_tan(j+1,1,k)) == 0.0) then
33050 dhdy = 0.0
3306 else
33070 dhdy = segment%grad_tan(j+1,1,k)
3308 endif
33090 if (dhdt*dhdx < 0.0) dhdt = 0.0
33100 cff_new = max((dhdx*dhdx) + (dhdy*dhdy), eps)
33110 rx_new = min(dhdt*dhdx, cff_new*rx_max)
33120 ry_new = min(cff_new,max(dhdt*dhdy,-cff_new))
33130 rx_tang_obl(I,J,k) = rx_new
33140 ry_tang_obl(I,J,k) = ry_new
33150 cff_tangential(I,J,k) = cff_new
3316 enddo
3317 endif
3318 enddo
33190 if (segment%oblique_tan) then
33200 do k=1,nz ; do J=segment%HI%JsdB,segment%HI%JedB
33210 rx_avg = rx_tang_obl(I,J,k)
33220 ry_avg = ry_tang_obl(I,J,k)
33230 cff_avg = cff_tangential(I,J,k)
3324 segment%tangential_vel(I,J,k) = ((cff_avg*v_new(i+1,J,k) + rx_avg*v_new(i+2,J,k)) - &
3325 (max(ry_avg,0.0)*segment%grad_tan(j,2,k) + &
3326 min(ry_avg,0.0)*segment%grad_tan(j+1,2,k))) / &
33270 (cff_avg + rx_avg)
3328 enddo ; enddo
3329 endif
33300 if (segment%nudged_tan) then
33310 do k=1,nz ; do J=segment%HI%JsdB,segment%HI%JedB
3332 ! dhdt gets set to 0 on inflow in oblique case
33330 if (rx_tang_obl(I,J,k) <= 0.0) then
33340 tau = segment%Velocity_nudging_timescale_in
3335 else
33360 tau = segment%Velocity_nudging_timescale_out
3337 endif
33380 gamma_2 = dt / (tau + dt)
3339 segment%tangential_vel(I,J,k) = (1.0 - gamma_2) * segment%tangential_vel(I,J,k) + &
33400 gamma_2 * segment%nudged_tangential_vel(I,J,k)
3341 enddo ; enddo
3342 endif
33430 if (segment%oblique_grad) then
33440 Js_obc = max(segment%HI%JsdB,G%jsd+1)
33450 Je_obc = min(segment%HI%JedB,G%jed-1)
33460 do k=1,nz ; do J=segment%HI%JsdB+1,segment%HI%JedB-1
33470 rx_avg = rx_tang_obl(I,J,k)
33480 ry_avg = ry_tang_obl(I,J,k)
33490 cff_avg = cff_tangential(I,J,k)
3350 segment%tangential_grad(I,J,k) = &
3351 ((cff_avg*(v_new(i+2,J,k) - v_new(i+1,J,k))*G%IdxBu(I+1,J) + &
3352 rx_avg*(v_new(i+3,J,k) - v_new(i+2,J,k))*G%IdxBu(I+2,J)) - &
3353 (max(ry_avg,0.0)*segment%grad_gradient(J,2,k) + &
3354 min(ry_avg,0.0)*segment%grad_gradient(J+1,2,k))) / &
33550 (cff_avg + rx_avg)
3356 enddo ; enddo
3357 endif
33580 if (segment%nudged_grad) then
33590 do k=1,nz ; do J=segment%HI%JsdB,segment%HI%JedB
3360 ! dhdt gets set to 0 on inflow in oblique case
33610 if (rx_tang_obl(I,J,k) <= 0.0) then
33620 tau = segment%Velocity_nudging_timescale_in
3363 else
33640 tau = segment%Velocity_nudging_timescale_out
3365 endif
33660 gamma_2 = dt / (tau + dt)
3367 segment%tangential_grad(I,J,k) = (1.0 - gamma_2) * segment%tangential_grad(I,J,k) + &
33680 gamma_2 * segment%nudged_tangential_grad(I,J,k)
3369 enddo ; enddo
3370 endif
33710 deallocate(rx_tang_obl)
33720 deallocate(ry_tang_obl)
33730 deallocate(cff_tangential)
3374 endif
3375 endif
3376
33770 if (segment%direction == OBC_DIRECTION_N) then
33780 J=segment%HI%JsdB
33790 if (J<G%HI%JscB) cycle
33800 do k=1,nz ; do i=segment%HI%isd,segment%HI%ied
33810 if (segment%radiation) then
33820 dhdt = (v_old(i,J-1,k) - v_new(i,J-1,k)) !old-new
33830 dhdy = (v_new(i,J-1,k) - v_new(i,J-2,k)) !in new time backward sashay for J-1
33840 ry_new = 0.0
33850 if (dhdt*dhdy > 0.0) ry_new = min( (dhdt/dhdy), ry_max)
33860 if (gamma_u < 1.0) then
33870 ry_avg = (1.0-gamma_u)*segment%ry_norm_rad(I,j,k) + gamma_u*ry_new
3388 else
33890 ry_avg = ry_new
3390 endif
33910 segment%ry_norm_rad(i,J,k) = ry_avg
3392 ! The new boundary value is interpolated between future interior
3393 ! value, v_new(J-1) and past boundary value but with barotropic
3394 ! accelerations, v_new(J).
33950 segment%normal_vel(i,J,k) = (v_new(i,J,k) + ry_avg*v_new(i,J-1,k)) / (1.0+ry_avg)
33960 if (gamma_u < 1.0) then
3397 ! Copy restart fields into 3-d arrays. This is an inefficient and temporary issues
3398 ! implemented as a work-around to limitations in restart capability
33990 OBC%ry_normal(i,J,k) = segment%ry_norm_rad(i,J,k)
3400 endif
34010 elseif (segment%oblique) then
34020 dhdt = (v_old(i,J-1,k) - v_new(i,J-1,k)) !old-new
34030 dhdy = (v_new(i,J-1,k) - v_new(i,J-2,k)) !in new time backward sashay for J-1
34040 if (dhdt*(segment%grad_normal(I,1,k) + segment%grad_normal(I-1,1,k)) > 0.0) then
34050 dhdx = segment%grad_normal(I-1,1,k)
34060 elseif (dhdt*(segment%grad_normal(I,1,k) + segment%grad_normal(I-1,1,k)) == 0.0) then
34070 dhdx = 0.0
3408 else
34090 dhdx = segment%grad_normal(I,1,k)
3410 endif
34110 if (dhdt*dhdy < 0.0) dhdt = 0.0
34120 cff_new = max((dhdx*dhdx) + (dhdy*dhdy), eps)
34130 ry_new = min(dhdt*dhdy, cff_new*ry_max)
34140 rx_new = min(cff_new,max(dhdt*dhdx,-cff_new))
34150 if (gamma_u < 1.0) then
34160 rx_avg = (1.0-gamma_u)*segment%rx_norm_obl(I,j,k) + gamma_u*rx_new
34170 ry_avg = (1.0-gamma_u)*segment%ry_norm_obl(i,J,k) + gamma_u*ry_new
34180 cff_avg = (1.0-gamma_u)*segment%cff_normal(i,J,k) + gamma_u*cff_new
3419 else
34200 rx_avg = rx_new
34210 ry_avg = ry_new
34220 cff_avg = cff_new
3423 endif
34240 segment%rx_norm_obl(i,J,k) = rx_avg
34250 segment%ry_norm_obl(i,J,k) = ry_avg
34260 segment%cff_normal(i,J,k) = cff_avg
3427 segment%normal_vel(i,J,k) = ((cff_avg*v_new(i,J,k) + ry_avg*v_new(i,J-1,k)) - &
3428 (max(rx_avg,0.0)*segment%grad_normal(I-1,2,k) +&
3429 min(rx_avg,0.0)*segment%grad_normal(I,2,k))) / &
34300 (cff_avg + ry_avg)
34310 if (gamma_u < 1.0) then
3432 ! Copy restart fields into 3-d arrays. This is an inefficient and temporary issues
3433 ! implemented as a work-around to limitations in restart capability
34340 OBC%rx_oblique_v(i,J,k) = segment%rx_norm_obl(i,J,k)
34350 OBC%ry_oblique_v(i,J,k) = segment%ry_norm_obl(i,J,k)
34360 OBC%cff_normal_v(i,J,k) = segment%cff_normal(i,J,k)
3437 endif
34380 elseif (segment%gradient) then
34390 segment%normal_vel(i,J,k) = v_new(i,J-1,k)
3440 endif
34410 if ((segment%radiation .or. segment%oblique) .and. segment%nudged) then
3442 ! dhdt gets set to 0 on inflow in oblique case
34430 if (dhdt*dhdy <= 0.0) then
34440 tau = segment%Velocity_nudging_timescale_in
3445 else
34460 tau = segment%Velocity_nudging_timescale_out
3447 endif
34480 gamma_2 = dt / (tau + dt)
3449 segment%normal_vel(i,J,k) = (1.0 - gamma_2) * segment%normal_vel(i,J,k) + &
34500 gamma_2 * segment%nudged_normal_vel(i,J,k)
3451 endif
3452 enddo ; enddo
34530 if (segment%radiation_tan .or. segment%radiation_grad) then
34540 J=segment%HI%JsdB
34550 allocate(ry_tang_rad(segment%HI%IsdB:segment%HI%IedB,segment%HI%JsdB:segment%HI%JedB,nz))
34560 do k=1,nz
34570 if (gamma_u < 1.0) then
34580 ry_tang_rad(segment%HI%IsdB,J,k) = segment%ry_norm_rad(segment%HI%isd,J,k)
34590 ry_tang_rad(segment%HI%IedB,J,k) = segment%ry_norm_rad(segment%HI%ied,J,k)
34600 do I=segment%HI%IsdB+1,segment%HI%IedB-1
34610 ry_tang_rad(I,J,k) = 0.5*(segment%ry_norm_rad(i,J,k) + segment%ry_norm_rad(i+1,J,k))
3462 enddo
3463 else
34640 do I=segment%HI%IsdB,segment%HI%IedB
34650 dhdt = u_old(I,j-1,k)-u_new(I,j-1,k) !old-new
34660 dhdy = u_new(I,j-1,k)-u_new(I,j-2,k) !in new time backward sashay for I-1
34670 ry_tang_rad(I,J,k) = 0.0
34680 if (dhdt*dhdy > 0.0) ry_tang_rad(I,J,k) = min( (dhdt/dhdy), rx_max) ! outward phase speed
3469 enddo
3470 endif
3471 enddo
34720 if (segment%radiation_tan) then
34730 do k=1,nz ; do I=segment%HI%IsdB,segment%HI%IedB
34740 ry_avg = ry_tang_rad(I,J,k)
34750 segment%tangential_vel(I,J,k) = (u_new(I,j,k) + ry_avg*u_new(I,j-1,k)) / (1.0+ry_avg)
3476 enddo ; enddo
3477 endif
34780 if (segment%nudged_tan) then
34790 do k=1,nz ; do I=segment%HI%IsdB,segment%HI%IedB
3480 ! dhdt gets set to 0 on inflow in oblique case
34810 if (ry_tang_rad(I,J,k) <= 0.0) then
34820 tau = segment%Velocity_nudging_timescale_in
3483 else
34840 tau = segment%Velocity_nudging_timescale_out
3485 endif
34860 gamma_2 = dt / (tau + dt)
3487 segment%tangential_vel(I,J,k) = (1.0 - gamma_2) * segment%tangential_vel(I,J,k) + &
34880 gamma_2 * segment%nudged_tangential_vel(I,J,k)
3489 enddo ; enddo
3490 endif
34910 if (segment%radiation_grad) then
34920 Is_obc = max(segment%HI%IsdB,G%isd+1)
34930 Ie_obc = min(segment%HI%IedB,G%ied-1)
34940 do k=1,nz ; do I=Is_obc,Ie_obc
34950 ry_avg = ry_tang_rad(I,J,k)
3496! if (G%mask2dCv(i,J-1) > 0.0 .and. G%mask2dCv(i+1,J-1) > 0.0) then
3497! ry_avg = 0.5*(v_new(i,J-1,k) + v_new(i+1,J-1,k) * dt * G%IdyBu(I,J-1))
3498! elseif (G%mask2dCv(i,J-1) > 0.0) then
3499! ry_avg = v_new(i,J-1,k) * dt *G%IdyBu(I,J-1)
3500! elseif (G%mask2dCv(i+1,J-1) > 0.0) then
3501! ry_avg = v_new(i+1,J-1,k) * dt *G%IdyBu(I,J-1)
3502! else
3503! ry_avg = 0.0
3504! endif
3505 segment%tangential_grad(I,J,k) = ((u_new(I,j,k) - u_new(I,j-1,k))*G%IdyBu(I,J-1) + &
35060 ry_avg*(u_new(I,j-1,k) - u_new(I,j-2,k))*G%IdyBu(I,J-2)) / (1.0+ry_avg)
3507 enddo ; enddo
3508 endif
35090 if (segment%nudged_grad) then
35100 do k=1,nz ; do I=segment%HI%IsdB,segment%HI%IedB
3511 ! dhdt gets set to 0 on inflow in oblique case
35120 if (ry_tang_rad(I,J,k) <= 0.0) then
35130 tau = segment%Velocity_nudging_timescale_in
3514 else
35150 tau = segment%Velocity_nudging_timescale_out
3516 endif
35170 gamma_2 = dt / (tau + dt)
3518 segment%tangential_grad(I,J,k) = (1.0 - gamma_2) * segment%tangential_grad(I,J,k) + &
35190 gamma_2 * segment%nudged_tangential_grad(I,J,k)
3520 enddo ; enddo
3521 endif
35220 deallocate(ry_tang_rad)
3523 endif
35240 if (segment%oblique_tan .or. segment%oblique_grad) then
35250 J=segment%HI%JsdB
35260 allocate(rx_tang_obl(segment%HI%IsdB:segment%HI%IedB,segment%HI%JsdB:segment%HI%JedB,nz))
35270 allocate(ry_tang_obl(segment%HI%IsdB:segment%HI%IedB,segment%HI%JsdB:segment%HI%JedB,nz))
35280 allocate(cff_tangential(segment%HI%IsdB:segment%HI%IedB,segment%HI%JsdB:segment%HI%JedB,nz))
35290 do k=1,nz
35300 if (gamma_u < 1.0) then
35310 rx_tang_obl(segment%HI%IsdB,J,k) = segment%rx_norm_obl(segment%HI%isd,J,k)
35320 rx_tang_obl(segment%HI%IedB,J,k) = segment%rx_norm_obl(segment%HI%ied,J,k)
35330 ry_tang_obl(segment%HI%IsdB,J,k) = segment%ry_norm_obl(segment%HI%isd,J,k)
35340 ry_tang_obl(segment%HI%IedB,J,k) = segment%ry_norm_obl(segment%HI%ied,J,k)
35350 cff_tangential(segment%HI%IsdB,J,k) = segment%cff_normal(segment%HI%isd,J,k)
35360 cff_tangential(segment%HI%IedB,J,k) = segment%cff_normal(segment%HI%ied,J,k)
35370 do I=segment%HI%IsdB+1,segment%HI%IedB-1
35380 rx_tang_obl(I,J,k) = 0.5*(segment%rx_norm_obl(i,J,k) + segment%rx_norm_obl(i+1,J,k))
35390 ry_tang_obl(I,J,k) = 0.5*(segment%ry_norm_obl(i,J,k) + segment%ry_norm_obl(i+1,J,k))
35400 cff_tangential(I,J,k) = 0.5*(segment%cff_normal(i,J,k) + segment%cff_normal(i+1,J,k))
3541 enddo
3542 else
35430 do I=segment%HI%IsdB,segment%HI%IedB
35440 dhdt = u_old(I,j,k)-u_new(I,j,k) !old-new
35450 dhdy = u_new(I,j,k)-u_new(I,j-1,k) !in new time backward sashay for I-1
35460 if (dhdt*(segment%grad_tan(i,1,k) + segment%grad_tan(i+1,1,k)) > 0.0) then
35470 dhdx = segment%grad_tan(i,1,k)
35480 elseif (dhdt*(segment%grad_tan(i,1,k) + segment%grad_tan(i+1,1,k)) == 0.0) then
35490 dhdx = 0.0
3550 else
35510 dhdx = segment%grad_tan(i+1,1,k)
3552 endif
35530 if (dhdt*dhdy < 0.0) dhdt = 0.0
35540 cff_new = max((dhdx*dhdx) + (dhdy*dhdy), eps)
35550 ry_new = min(dhdt*dhdy, cff_new*ry_max)
35560 rx_new = min(cff_new,max(dhdt*dhdx,-cff_new))
35570 rx_tang_obl(I,J,k) = rx_new
35580 ry_tang_obl(I,J,k) = ry_new
35590 cff_tangential(I,J,k) = cff_new
3560 enddo
3561 endif
3562 enddo
35630 if (segment%oblique_tan) then
35640 do k=1,nz ; do I=segment%HI%IsdB,segment%HI%IedB
35650 rx_avg = rx_tang_obl(I,J,k)
35660 ry_avg = ry_tang_obl(I,J,k)
35670 cff_avg = cff_tangential(I,J,k)
3568 segment%tangential_vel(I,J,k) = ((cff_avg*u_new(I,j,k) + ry_avg*u_new(I,j-1,k)) - &
3569 (max(rx_avg,0.0)*segment%grad_tan(i,2,k) + &
3570 min(rx_avg,0.0)*segment%grad_tan(i+1,2,k))) / &
35710 (cff_avg + ry_avg)
3572 enddo ; enddo
3573 endif
35740 if (segment%nudged_tan) then
35750 do k=1,nz ; do I=segment%HI%IsdB,segment%HI%IedB
3576 ! dhdt gets set to 0 on inflow in oblique case
35770 if (ry_tang_obl(I,J,k) <= 0.0) then
35780 tau = segment%Velocity_nudging_timescale_in
3579 else
35800 tau = segment%Velocity_nudging_timescale_out
3581 endif
35820 gamma_2 = dt / (tau + dt)
3583 segment%tangential_vel(I,J,k) = (1.0 - gamma_2) * segment%tangential_vel(I,J,k) + &
35840 gamma_2 * segment%nudged_tangential_vel(I,J,k)
3585 enddo ; enddo
3586 endif
35870 if (segment%oblique_grad) then
35880 Is_obc = max(segment%HI%IsdB,G%isd+1)
35890 Ie_obc = min(segment%HI%IedB,G%ied-1)
35900 do k=1,nz ; do I=segment%HI%IsdB+1,segment%HI%IedB-1
35910 rx_avg = rx_tang_obl(I,J,k)
35920 ry_avg = ry_tang_obl(I,J,k)
35930 cff_avg = cff_tangential(I,J,k)
3594 segment%tangential_grad(I,J,k) = &
3595 ((cff_avg*(u_new(I,j,k) - u_new(I,j-1,k))*G%IdyBu(I,J-1) + &
3596 ry_avg*(u_new(I,j-1,k) - u_new(I,j-2,k))*G%IdyBu(I,J-2)) - &
3597 (max(rx_avg,0.0)*segment%grad_gradient(I,2,k) + &
3598 min(rx_avg,0.0)*segment%grad_gradient(I+1,2,k))) / &
35990 (cff_avg + ry_avg)
3600 enddo ; enddo
3601 endif
36020 if (segment%nudged_grad) then
36030 do k=1,nz ; do I=segment%HI%IsdB,segment%HI%IedB
3604 ! dhdt gets set to 0 on inflow in oblique case
36050 if (ry_tang_obl(I,J,k) <= 0.0) then
36060 tau = segment%Velocity_nudging_timescale_in
3607 else
36080 tau = segment%Velocity_nudging_timescale_out
3609 endif
36100 gamma_2 = dt / (tau + dt)
3611 segment%tangential_grad(I,J,k) = (1.0 - gamma_2) * segment%tangential_grad(I,J,k) + &
36120 gamma_2 * segment%nudged_tangential_grad(I,J,k)
3613 enddo ; enddo
3614 endif
36150 deallocate(rx_tang_obl)
36160 deallocate(ry_tang_obl)
36170 deallocate(cff_tangential)
3618 endif
3619 endif
3620
36210 if (segment%direction == OBC_DIRECTION_S) then
36220 J=segment%HI%JsdB
36230 if (J>G%HI%JecB) cycle
36240 do k=1,nz ; do i=segment%HI%isd,segment%HI%ied
36250 if (segment%radiation) then
36260 dhdt = (v_old(i,J+1,k) - v_new(i,J+1,k)) !old-new
36270 dhdy = (v_new(i,J+1,k) - v_new(i,J+2,k)) !in new time backward sashay for J-1
36280 ry_new = 0.0
36290 if (dhdt*dhdy > 0.0) ry_new = min( (dhdt/dhdy), ry_max)
36300 if (gamma_u < 1.0) then
36310 ry_avg = (1.0-gamma_u)*segment%ry_norm_rad(I,j,k) + gamma_u*ry_new
3632 else
36330 ry_avg = ry_new
3634 endif
36350 segment%ry_norm_rad(i,J,k) = ry_avg
3636 ! The new boundary value is interpolated between future interior
3637 ! value, v_new(J+1) and past boundary value but with barotropic
3638 ! accelerations, v_new(J).
36390 segment%normal_vel(i,J,k) = (v_new(i,J,k) + ry_avg*v_new(i,J+1,k)) / (1.0+ry_avg)
36400 if (gamma_u < 1.0) then
3641 ! Copy restart fields into 3-d arrays. This is an inefficient and temporary issues
3642 ! implemented as a work-around to limitations in restart capability
36430 OBC%ry_normal(i,J,k) = segment%ry_norm_rad(i,J,k)
3644 endif
36450 elseif (segment%oblique) then
36460 dhdt = (v_old(i,J+1,k) - v_new(i,J+1,k)) !old-new
36470 dhdy = (v_new(i,J+1,k) - v_new(i,J+2,k)) !in new time backward sashay for J-1
36480 if (dhdt*(segment%grad_normal(I,1,k) + segment%grad_normal(I-1,1,k)) > 0.0) then
36490 dhdx = segment%grad_normal(I-1,1,k)
36500 elseif (dhdt*(segment%grad_normal(I,1,k) + segment%grad_normal(I-1,1,k)) == 0.0) then
36510 dhdx = 0.0
3652 else
36530 dhdx = segment%grad_normal(I,1,k)
3654 endif
36550 if (dhdt*dhdy < 0.0) dhdt = 0.0
3656
36570 cff_new = max((dhdx*dhdx) + (dhdy*dhdy), eps)
36580 ry_new = min(dhdt*dhdy, cff_new*ry_max)
36590 rx_new = min(cff_new,max(dhdt*dhdx,-cff_new))
36600 if (gamma_u < 1.0) then
36610 rx_avg = (1.0-gamma_u)*segment%rx_norm_obl(i,J,k) + gamma_u*rx_new
36620 ry_avg = (1.0-gamma_u)*segment%ry_norm_obl(i,J,k) + gamma_u*ry_new
36630 cff_avg = (1.0-gamma_u)*segment%cff_normal(i,J,k) + gamma_u*cff_new
3664 else
36650 rx_avg = rx_new
36660 ry_avg = ry_new
36670 cff_avg = cff_new
3668 endif
36690 segment%rx_norm_obl(i,J,k) = rx_avg
36700 segment%ry_norm_obl(i,J,k) = ry_avg
36710 segment%cff_normal(i,J,k) = cff_avg
3672 segment%normal_vel(i,J,k) = ((cff_avg*v_new(i,J,k) + ry_avg*v_new(i,J+1,k)) - &
3673 (max(rx_avg,0.0)*segment%grad_normal(I-1,2,k) + &
3674 min(rx_avg,0.0)*segment%grad_normal(I,2,k))) / &
36750 (cff_avg + ry_avg)
36760 if (gamma_u < 1.0) then
3677 ! Copy restart fields into 3-d arrays. This is an inefficient and temporary issues
3678 ! implemented as a work-around to limitations in restart capability
36790 OBC%rx_oblique_v(i,J,k) = segment%rx_norm_obl(i,J,k)
36800 OBC%ry_oblique_v(i,J,k) = segment%ry_norm_obl(i,J,k)
36810 OBC%cff_normal_v(i,J,k) = segment%cff_normal(i,J,k)
3682 endif
36830 elseif (segment%gradient) then
36840 segment%normal_vel(i,J,k) = v_new(i,J+1,k)
3685 endif
36860 if ((segment%radiation .or. segment%oblique) .and. segment%nudged) then
3687 ! dhdt gets set to 0 on inflow in oblique case
36880 if (dhdt*dhdy <= 0.0) then
36890 tau = segment%Velocity_nudging_timescale_in
3690 else
36910 tau = segment%Velocity_nudging_timescale_out
3692 endif
36930 gamma_2 = dt / (tau + dt)
3694 segment%normal_vel(i,J,k) = (1.0 - gamma_2) * segment%normal_vel(i,J,k) + &
36950 gamma_2 * segment%nudged_normal_vel(i,J,k)
3696 endif
3697 enddo ; enddo
36980 if (segment%radiation_tan .or. segment%radiation_grad) then
36990 J=segment%HI%JsdB
37000 allocate(ry_tang_rad(segment%HI%IsdB:segment%HI%IedB,segment%HI%JsdB:segment%HI%JedB,nz))
37010 do k=1,nz
37020 if (gamma_u < 1.0) then
37030 ry_tang_rad(segment%HI%IsdB,J,k) = segment%ry_norm_rad(segment%HI%isd,J,k)
37040 ry_tang_rad(segment%HI%IedB,J,k) = segment%ry_norm_rad(segment%HI%ied,J,k)
37050 do I=segment%HI%IsdB+1,segment%HI%IedB-1
37060 ry_tang_rad(I,J,k) = 0.5*(segment%ry_norm_rad(i,J,k) + segment%ry_norm_rad(i+1,J,k))
3707 enddo
3708 else
37090 do I=segment%HI%IsdB,segment%HI%IedB
37100 dhdt = u_old(I,j+1,k)-u_new(I,j+1,k) !old-new
37110 dhdy = u_new(I,j+1,k)-u_new(I,j+2,k) !in new time backward sashay for I-1
37120 ry_tang_rad(I,J,k) = 0.0
37130 if (dhdt*dhdy > 0.0) ry_tang_rad(I,J,k) = min( (dhdt/dhdy), rx_max) ! outward phase speed
3714 enddo
3715 endif
3716 enddo
37170 if (segment%radiation_tan) then
37180 do k=1,nz ; do I=segment%HI%IsdB,segment%HI%IedB
37190 ry_avg = ry_tang_rad(I,J,k)
37200 segment%tangential_vel(I,J,k) = (u_new(I,j+1,k) + ry_avg*u_new(I,j+2,k)) / (1.0+ry_avg)
3721 enddo ; enddo
3722 endif
37230 if (segment%nudged_tan) then
37240 do k=1,nz ; do I=segment%HI%IsdB,segment%HI%IedB
3725 ! dhdt gets set to 0 on inflow in oblique case
37260 if (ry_tang_rad(I,J,k) <= 0.0) then
37270 tau = segment%Velocity_nudging_timescale_in
3728 else
37290 tau = segment%Velocity_nudging_timescale_out
3730 endif
37310 gamma_2 = dt / (tau + dt)
3732 segment%tangential_vel(I,J,k) = (1.0 - gamma_2) * segment%tangential_vel(I,J,k) + &
37330 gamma_2 * segment%nudged_tangential_vel(I,J,k)
3734 enddo ; enddo
3735 endif
37360 if (segment%radiation_grad) then
37370 Is_obc = max(segment%HI%IsdB,G%isd+1)
37380 Ie_obc = min(segment%HI%IedB,G%ied-1)
37390 do k=1,nz ; do I=Is_obc,Ie_obc
37400 ry_avg = ry_tang_rad(I,J,k)
3741! if (G%mask2dCv(i,J+1) > 0.0 .and. G%mask2dCv(i+1,J+1) > 0.0) then
3742! ry_avg = 0.5*(v_new(i,J+1,k) + v_new(i+1,J+1,k)) * dt * G%IdyBu(I,J+1)
3743! elseif (G%mask2dCv(i,J+1) > 0.0) then
3744! ry_avg = v_new(i,J+1,k) * dt * G%IdyBu(I,J+1)
3745! elseif (G%mask2dCv(i+1,J+1) > 0.0) then
3746! ry_avg = v_new(i+1,J+1,k) * dt * G%IdyBu(I,J+1)
3747! else
3748! ry_avg = 0.0
3749! endif
3750 segment%tangential_grad(I,J,k) = ((u_new(I,j+2,k) - u_new(I,j+1,k))*G%IdyBu(I,J+1) + &
37510 ry_avg*(u_new(I,j+3,k) - u_new(I,j+2,k))*G%IdyBu(I,J+2)) / (1.0+ry_avg)
3752 enddo ; enddo
3753 endif
37540 if (segment%nudged_grad) then
37550 do k=1,nz ; do I=segment%HI%IsdB,segment%HI%IedB
3756 ! dhdt gets set to 0 on inflow in oblique case
37570 if (ry_tang_rad(I,J,k) <= 0.0) then
37580 tau = segment%Velocity_nudging_timescale_in
3759 else
37600 tau = segment%Velocity_nudging_timescale_out
3761 endif
37620 gamma_2 = dt / (tau + dt)
3763 segment%tangential_grad(I,J,k) = (1.0 - gamma_2) * segment%tangential_grad(I,J,k) + &
37640 gamma_2 * segment%nudged_tangential_grad(I,J,k)
3765 enddo ; enddo
3766 endif
37670 deallocate(ry_tang_rad)
3768 endif
37690 if (segment%oblique_tan .or. segment%oblique_grad) then
37700 J=segment%HI%JsdB
37710 allocate(rx_tang_obl(segment%HI%IsdB:segment%HI%IedB,segment%HI%JsdB:segment%HI%JedB,nz))
37720 allocate(ry_tang_obl(segment%HI%IsdB:segment%HI%IedB,segment%HI%JsdB:segment%HI%JedB,nz))
37730 allocate(cff_tangential(segment%HI%IsdB:segment%HI%IedB,segment%HI%JsdB:segment%HI%JedB,nz))
37740 do k=1,nz
37750 if (gamma_u < 1.0) then
37760 rx_tang_obl(segment%HI%IsdB,J,k) = segment%rx_norm_obl(segment%HI%isd,J,k)
37770 rx_tang_obl(segment%HI%IedB,J,k) = segment%rx_norm_obl(segment%HI%ied,J,k)
37780 ry_tang_obl(segment%HI%IsdB,J,k) = segment%ry_norm_obl(segment%HI%isd,J,k)
37790 ry_tang_obl(segment%HI%IedB,J,k) = segment%ry_norm_obl(segment%HI%ied,J,k)
37800 cff_tangential(segment%HI%IsdB,J,k) = segment%cff_normal(segment%HI%isd,J,k)
37810 cff_tangential(segment%HI%IedB,J,k) = segment%cff_normal(segment%HI%ied,J,k)
37820 do I=segment%HI%IsdB+1,segment%HI%IedB-1
37830 rx_tang_obl(I,J,k) = 0.5*(segment%rx_norm_obl(i,J,k) + segment%rx_norm_obl(i+1,J,k))
37840 ry_tang_obl(I,J,k) = 0.5*(segment%ry_norm_obl(i,J,k) + segment%ry_norm_obl(i+1,J,k))
37850 cff_tangential(I,J,k) = 0.5*(segment%cff_normal(i,J,k) + segment%cff_normal(i+1,J,k))
3786 enddo
3787 else
37880 do I=segment%HI%IsdB,segment%HI%IedB
37890 dhdt = u_old(I,j+1,k)-u_new(I,j+1,k) !old-new
37900 dhdy = u_new(I,j+1,k)-u_new(I,j+2,k) !in new time backward sashay for I-1
37910 if (dhdt*(segment%grad_tan(i,1,k) + segment%grad_tan(i+1,1,k)) > 0.0) then
37920 dhdx = segment%grad_tan(i,1,k)
37930 elseif (dhdt*(segment%grad_tan(i,1,k) + segment%grad_tan(i+1,1,k)) == 0.0) then
37940 dhdx = 0.0
3795 else
37960 dhdx = segment%grad_tan(i+1,1,k)
3797 endif
37980 if (dhdt*dhdy < 0.0) dhdt = 0.0
37990 cff_new = max((dhdx*dhdx) + (dhdy*dhdy), eps)
38000 ry_new = min(dhdt*dhdy, cff_new*ry_max)
38010 rx_new = min(cff_new,max(dhdt*dhdx,-cff_new))
38020 rx_tang_obl(I,J,k) = rx_new
38030 ry_tang_obl(I,J,k) = ry_new
38040 cff_tangential(I,J,k) = cff_new
3805 enddo
3806 endif
3807 enddo
38080 if (segment%oblique_tan) then
38090 do k=1,nz ; do I=segment%HI%IsdB,segment%HI%IedB
38100 rx_avg = rx_tang_obl(I,J,k)
38110 ry_avg = ry_tang_obl(I,J,k)
38120 cff_avg = cff_tangential(I,J,k)
3813 segment%tangential_vel(I,J,k) = ((cff_avg*u_new(I,j+1,k) + ry_avg*u_new(I,j+2,k)) - &
3814 (max(rx_avg,0.0)*segment%grad_tan(i,2,k) + &
3815 min(rx_avg,0.0)*segment%grad_tan(i+1,2,k)) ) / &
38160 (cff_avg + ry_avg)
3817 enddo ; enddo
3818 endif
38190 if (segment%nudged_tan) then
38200 do k=1,nz ; do I=segment%HI%IsdB,segment%HI%IedB
3821 ! dhdt gets set to 0 on inflow in oblique case
38220 if (ry_tang_obl(I,J,k) <= 0.0) then
38230 tau = segment%Velocity_nudging_timescale_in
3824 else
38250 tau = segment%Velocity_nudging_timescale_out
3826 endif
38270 gamma_2 = dt / (tau + dt)
3828 segment%tangential_vel(I,J,k) = (1.0 - gamma_2) * segment%tangential_vel(I,J,k) + &
38290 gamma_2 * segment%nudged_tangential_vel(I,J,k)
3830 enddo ; enddo
3831 endif
38320 if (segment%oblique_grad) then
38330 Is_obc = max(segment%HI%IsdB,G%isd+1)
38340 Ie_obc = min(segment%HI%IedB,G%ied-1)
38350 do k=1,nz ; do I=segment%HI%IsdB+1,segment%HI%IedB-1
38360 rx_avg = rx_tang_obl(I,J,k)
38370 ry_avg = ry_tang_obl(I,J,k)
38380 cff_avg = cff_tangential(I,J,k)
3839 segment%tangential_grad(I,J,k) = &
3840 ((cff_avg*(u_new(I,j+2,k) - u_new(I,j+1,k))*G%IdyBu(I,J+1) + &
3841 ry_avg*(u_new(I,j+3,k) - u_new(I,j+2,k))*G%IdyBu(I,J+2)) - &
3842 (max(rx_avg,0.0)*segment%grad_gradient(i,2,k) + &
3843 min(rx_avg,0.0)*segment%grad_gradient(i+1,2,k))) / &
38440 (cff_avg + ry_avg)
3845 enddo ; enddo
3846 endif
38470 if (segment%nudged_grad) then
38480 do k=1,nz ; do J=segment%HI%JsdB,segment%HI%JedB
3849 ! dhdt gets set to 0 on inflow in oblique case
38500 if (ry_tang_obl(I,J,k) <= 0.0) then
38510 tau = segment%Velocity_nudging_timescale_in
3852 else
38530 tau = segment%Velocity_nudging_timescale_out
3854 endif
38550 gamma_2 = dt / (tau + dt)
3856 segment%tangential_grad(I,J,k) = (1.0 - gamma_2) * segment%tangential_grad(I,J,k) + &
38570 gamma_2 * segment%nudged_tangential_grad(I,J,k)
3858 enddo ; enddo
3859 endif
38600 deallocate(rx_tang_obl)
38610 deallocate(ry_tang_obl)
38620 deallocate(cff_tangential)
3863 endif
3864 endif
3865 enddo
3866
3867 ! Actually update u_new, v_new
38680 call open_boundary_apply_normal_flow(OBC, G, GV, u_new, v_new)
3869
38700 call pass_vector(u_new, v_new, G%Domain, clock=id_clock_pass)
3871
38720 if (OBC%debug) then
38730 sym = G%Domain%symmetric
38740 if (OBC%radiation_BCs_exist_globally) then
3875 call uvchksum("radiation_OBCs: OBC%r[xy]_normal", OBC%rx_normal, OBC%ry_normal, G%HI, &
38760 haloshift=0, symmetric=sym, scalar_pair=.true., unscale=1.0)
3877 endif
38780 if (OBC%oblique_BCs_exist_globally) then
3879 call uvchksum("radiation_OBCs: OBC%r[xy]_oblique_[uv]", OBC%rx_oblique_u, OBC%ry_oblique_v, G%HI, &
38800 haloshift=0, symmetric=sym, scalar_pair=.true., unscale=1.0/US%L_T_to_m_s**2)
3881 call uvchksum("radiation_OBCs: OBC%r[yx]_oblique_[uv]", OBC%ry_oblique_u, OBC%rx_oblique_v, G%HI, &
38820 haloshift=0, symmetric=sym, scalar_pair=.true., unscale=1.0/US%L_T_to_m_s**2)
3883 call uvchksum("radiation_OBCs: OBC%cff_normal_[uv]", OBC%cff_normal_u, OBC%cff_normal_v, G%HI, &
38840 haloshift=0, symmetric=sym, scalar_pair=.true., unscale=1.0/US%L_T_to_m_s**2)
3885 endif
38860 if ((OBC%ntr > 0) .and. allocated(OBC%tres_x) .and. allocated(OBC%tres_y)) then
38870 do m=1,OBC%ntr
38880 write(var_num,'(I3.3)') m
3889 call uvchksum("radiation_OBCs: OBC%tres_[xy]_"//var_num, OBC%tres_x(:,:,:,m), OBC%tres_y(:,:,:,m), G%HI, &
38900 haloshift=0, symmetric=sym, scalar_pair=.true., unscale=1.0)
3891 enddo
3892 endif
3893 endif
3894
38950end subroutine radiation_open_bdry_conds
3896
3897!> Applies OBC values stored in segments to 3d u,v fields
38980subroutine open_boundary_apply_normal_flow(OBC, G, GV, u, v)
3899 ! Arguments
3900 type(ocean_OBC_type), pointer :: OBC !< Open boundary control structure
3901 type(ocean_grid_type), intent(inout) :: G !< Ocean grid structure
3902 type(verticalGrid_type), intent(in) :: GV !< The ocean's vertical grid structure
3903 real, dimension(SZIB_(G),SZJ_(G),SZK_(GV)), intent(inout) :: u !< u field to update on open
3904 !! boundaries [L T-1 ~> m s-1]
3905 real, dimension(SZI_(G),SZJB_(G),SZK_(GV)), intent(inout) :: v !< v field to update on open
3906 !! boundaries [L T-1 ~> m s-1]
3907 ! Local variables
3908 integer :: i, j, k, n
3909 type(OBC_segment_type), pointer :: segment => NULL()
3910
39110 if (.not.associated(OBC)) return ! Bail out if OBC is not available
3912
39130 do n=1,OBC%number_of_segments
39140 segment => OBC%segment(n)
39150 if (.not. segment%on_pe) then
39160 cycle
39170 elseif (segment%radiation .or. segment%oblique .or. segment%gradient) then
39180 if (segment%is_E_or_W) then
39190 I=segment%HI%IsdB
39200 do k=1,GV%ke ; do j=segment%HI%jsd,segment%HI%jed
39210 u(I,j,k) = segment%normal_vel(I,j,k)
3922 enddo ; enddo
39230 elseif (segment%is_N_or_S) then
39240 J=segment%HI%JsdB
39250 do k=1,GV%ke ; do i=segment%HI%isd,segment%HI%ied
39260 v(i,J,k) = segment%normal_vel(i,J,k)
3927 enddo ; enddo
3928 endif
3929 endif
3930 enddo
3931
3932end subroutine open_boundary_apply_normal_flow
3933
3934!> Applies zero values to 3d u,v fields on OBC segments
39350subroutine open_boundary_zero_normal_flow(OBC, G, GV, u, v)
3936 ! Arguments
3937 type(ocean_OBC_type), pointer :: OBC !< Open boundary control structure
3938 type(ocean_grid_type), intent(inout) :: G !< Ocean grid structure
3939 type(verticalGrid_type), intent(in) :: GV !< The ocean's vertical grid structure
3940 real, dimension(SZIB_(G),SZJ_(G),SZK_(GV)), intent(inout) :: u !< u field to update on open boundaries [arbitrary]
3941 real, dimension(SZI_(G),SZJB_(G),SZK_(GV)), intent(inout) :: v !< v field to update on open boundaries [arbitrary]
3942 ! Local variables
3943 integer :: i, j, k, n
3944 type(OBC_segment_type), pointer :: segment => NULL()
3945
39460 if (.not.associated(OBC)) return ! Bail out if OBC is not available
3947
39480 do n=1,OBC%number_of_segments
39490 segment => OBC%segment(n)
39500 if (.not. segment%on_pe) then
39510 cycle
39520 elseif (segment%is_E_or_W) then
39530 I=segment%HI%IsdB
39540 do k=1,GV%ke ; do j=segment%HI%jsd,segment%HI%jed
39550 u(I,j,k) = 0.
3956 enddo ; enddo
39570 elseif (segment%is_N_or_S) then
39580 J=segment%HI%JsdB
39590 do k=1,GV%ke ; do i=segment%HI%isd,segment%HI%ied
39600 v(i,J,k) = 0.
3961 enddo ; enddo
3962 endif
3963 enddo
3964
3965end subroutine open_boundary_zero_normal_flow
3966
3967!> Calculate the tangential gradient of the normal flow at the boundary q-points.
39680subroutine gradient_at_q_points(G, GV, segment, uvel, vvel)
3969 type(ocean_grid_type), intent(in) :: G !< Ocean grid structure
3970 type(verticalGrid_type), intent(in) :: GV !< The ocean's vertical grid structure
3971 type(OBC_segment_type), intent(inout) :: segment !< OBC segment structure
3972 real, dimension(SZIB_(G),SZJ_(G),SZK_(GV)), intent(in) :: uvel !< zonal velocity [L T-1 ~> m s-1]
3973 real, dimension(SZI_(G),SZJB_(G),SZK_(GV)), intent(in) :: vvel !< meridional velocity [L T-1 ~> m s-1]
3974 integer :: i,j,k
3975
39760 if (.not. segment%on_pe) return
3977
39780 if (segment%is_E_or_W) then
39790 if (segment%direction == OBC_DIRECTION_E) then
39800 I=segment%HI%isdB
39810 do k=1,GV%ke
39820 do J=max(segment%HI%JsdB, G%HI%JsdB+1),min(segment%HI%JedB, G%HI%JedB-1)
39830 segment%grad_normal(J,1,k) = (uvel(I-1,j+1,k)-uvel(I-1,j,k)) * G%mask2dBu(I-1,J)
39840 segment%grad_normal(J,2,k) = (uvel(I,j+1,k)-uvel(I,j,k)) * G%mask2dBu(I,J)
3985 enddo
3986 enddo
39870 if (segment%oblique_tan) then
39880 do k=1,GV%ke
39890 do J=max(segment%HI%jsd-1, G%HI%jsd),min(segment%HI%jed+1, G%HI%jed)
39900 segment%grad_tan(j,1,k) = (vvel(i-1,J,k)-vvel(i-1,J-1,k)) * G%mask2dT(i-1,j)
39910 segment%grad_tan(j,2,k) = (vvel(i,J,k)-vvel(i,J-1,k)) * G%mask2dT(i,j)
3992 enddo
3993 enddo
3994 endif
39950 if (segment%oblique_grad) then
39960 do k=1,GV%ke
39970 do J=max(segment%HI%jsd, G%HI%jsd+1),min(segment%HI%jed, G%HI%jed-1)
3998 segment%grad_gradient(j,1,k) = (((vvel(i-1,J,k) - vvel(i-2,J,k))*G%IdxBu(I-2,J)) - &
39990 ((vvel(i-1,J-1,k) - vvel(i-2,J-1,k))*G%IdxBu(I-2,J-1))) * G%mask2dCu(I-2,j)
4000 segment%grad_gradient(j,2,k) = (((vvel(i,J,k) - vvel(i-1,J,k))*G%IdxBu(I-1,J)) - &
40010 ((vvel(i,J-1,k) - vvel(i-1,J-1,k))*G%IdxBu(I-1,J-1))) * G%mask2dCu(I-1,j)
4002 enddo
4003 enddo
4004 endif
4005 else ! western segment
40060 I=segment%HI%isdB
40070 do k=1,GV%ke
40080 do J=max(segment%HI%JsdB, G%HI%JsdB+1),min(segment%HI%JedB, G%HI%JedB-1)
40090 segment%grad_normal(J,1,k) = (uvel(I+1,j+1,k)-uvel(I+1,j,k)) * G%mask2dBu(I+1,J)
40100 segment%grad_normal(J,2,k) = (uvel(I,j+1,k)-uvel(I,j,k)) * G%mask2dBu(I,J)
4011 enddo
4012 enddo
40130 if (segment%oblique_tan) then
40140 do k=1,GV%ke
40150 do J=max(segment%HI%jsd-1, G%HI%jsd),min(segment%HI%jed+1, G%HI%jed)
40160 segment%grad_tan(j,1,k) = (vvel(i+2,J,k)-vvel(i+2,J-1,k)) * G%mask2dT(i+2,j)
40170 segment%grad_tan(j,2,k) = (vvel(i+1,J,k)-vvel(i+1,J-1,k)) * G%mask2dT(i+1,j)
4018 enddo
4019 enddo
4020 endif
40210 if (segment%oblique_grad) then
40220 do k=1,GV%ke
40230 do J=max(segment%HI%jsd, G%HI%jsd+1),min(segment%HI%jed, G%HI%jed-1)
4024 segment%grad_gradient(j,1,k) = (((vvel(i+3,J,k) - vvel(i+2,J,k))*G%IdxBu(I+2,J)) - &
40250 ((vvel(i+3,J-1,k) - vvel(i+2,J-1,k))*G%IdxBu(I+2,J-1))) * G%mask2dCu(I+2,j)
4026 segment%grad_gradient(j,2,k) = (((vvel(i+2,J,k) - vvel(i+1,J,k))*G%IdxBu(I+1,J)) - &
40270 ((vvel(i+2,J-1,k) - vvel(i+1,J-1,k))*G%IdxBu(I+1,J-1))) * G%mask2dCu(I+1,j)
4028 enddo
4029 enddo
4030 endif
4031 endif
40320 elseif (segment%is_N_or_S) then
40330 if (segment%direction == OBC_DIRECTION_N) then
40340 J=segment%HI%jsdB
40350 do k=1,GV%ke
40360 do I=max(segment%HI%IsdB, G%HI%IsdB+1),min(segment%HI%IedB, G%HI%IedB-1)
40370 segment%grad_normal(I,1,k) = (vvel(i+1,J-1,k)-vvel(i,J-1,k)) * G%mask2dBu(I,J-1)
40380 segment%grad_normal(I,2,k) = (vvel(i+1,J,k)-vvel(i,J,k)) * G%mask2dBu(I,J)
4039 enddo
4040 enddo
40410 if (segment%oblique_tan) then
40420 do k=1,GV%ke
40430 do I=max(segment%HI%isd-1, G%HI%isd),min(segment%HI%ied+1, G%HI%ied)
40440 segment%grad_tan(i,1,k) = (uvel(I,j-1,k)-uvel(I-1,j-1,k)) * G%mask2dT(i,j-1)
40450 segment%grad_tan(i,2,k) = (uvel(I,j,k)-uvel(I-1,j,k)) * G%mask2dT(i,j)
4046 enddo
4047 enddo
4048 endif
40490 if (segment%oblique_grad) then
40500 do k=1,GV%ke
40510 do I=max(segment%HI%isd, G%HI%isd+1),min(segment%HI%ied, G%HI%ied-1)
4052 segment%grad_gradient(i,1,k) = (((uvel(I,j-1,k) - uvel(I,j-2,k))*G%IdyBu(I,J-2)) - &
40530 ((uvel(I-1,j-1,k) - uvel(I-1,j-2,k))*G%IdyBu(I-1,J-2))) * G%mask2dCv(i,J-2)
4054 segment%grad_gradient(i,2,k) = (((uvel(I,j,k) - uvel(I,j-1,k))*G%IdyBu(I,J-1)) - &
40550 ((uvel(I-1,j,k) - uvel(I-1,j-1,k))*G%IdyBu(I-1,J-1))) * G%mask2dCv(i,J-1)
4056 enddo
4057 enddo
4058 endif
4059 else ! south segment
40600 J=segment%HI%jsdB
40610 do k=1,GV%ke
40620 do I=max(segment%HI%IsdB, G%HI%IsdB+1),min(segment%HI%IedB, G%HI%IedB-1)
40630 segment%grad_normal(I,1,k) = (vvel(i+1,J+1,k)-vvel(i,J+1,k)) * G%mask2dBu(I,J+1)
40640 segment%grad_normal(I,2,k) = (vvel(i+1,J,k)-vvel(i,J,k)) * G%mask2dBu(I,J)
4065 enddo
4066 enddo
40670 if (segment%oblique_tan) then
40680 do k=1,GV%ke
40690 do I=max(segment%HI%isd-1, G%HI%isd),min(segment%HI%ied+1, G%HI%ied)
40700 segment%grad_tan(i,1,k) = (uvel(I,j+2,k)-uvel(I-1,j+2,k)) * G%mask2dT(i,j+2)
40710 segment%grad_tan(i,2,k) = (uvel(I,j+1,k)-uvel(I-1,j+1,k)) * G%mask2dT(i,j+1)
4072 enddo
4073 enddo
4074 endif
40750 if (segment%oblique_grad) then
40760 do k=1,GV%ke
40770 do I=max(segment%HI%isd, G%HI%isd+1),min(segment%HI%ied, G%HI%ied-1)
4078 segment%grad_gradient(i,1,k) = (((uvel(I,j+3,k) - uvel(I,j+2,k))*G%IdyBu(I,J+2)) - &
40790 ((uvel(I-1,j+3,k) - uvel(I-1,j+2,k))*G%IdyBu(I-1,J+2))) * G%mask2dCv(i,J+2)
4080 segment%grad_gradient(i,2,k) = (((uvel(I,j+2,k) - uvel(I,j+1,k))*G%IdyBu(I,J+1)) - &
40810 ((uvel(I-1,j+2,k) - uvel(I-1,j+1,k))*G%IdyBu(I-1,J+1))) * G%mask2dCv(i,J+1)
4082 enddo
4083 enddo
4084 endif
4085 endif
4086 endif
4087
4088end subroutine gradient_at_q_points
4089
4090
4091!> Return the field number on the segment for the named field, or -1 if there is no field with that name.
40920function lookup_seg_field(OBC_seg, field)
4093 type(OBC_segment_type), intent(in) :: OBC_seg !< OBC segment
4094 character(len=32), intent(in) :: field !< The field name
4095 integer :: lookup_seg_field
4096 ! Local variables
4097 integer :: n
4098
40990 lookup_seg_field = -1
41000 do n=1,OBC_seg%num_fields
41010 if (trim(field) == OBC_seg%field(n)%name) then
41020 lookup_seg_field = n
41030 return
4104 endif
4105 enddo
4106
41070end function lookup_seg_field
4108
4109!> Return the tracer index from its name
41100function get_tracer_index(OBC_seg,tr_name)
4111 type(OBC_segment_type), pointer :: OBC_seg !< OBC segment
4112 character(len=*), intent(in) :: tr_name !< The field name
4113 integer :: get_tracer_index, it
41140 get_tracer_index = -1
41150 it = 1
41160 do while(allocated(OBC_seg%tr_Reg%Tr(it)%t))
41170 if (trim(OBC_seg%tr_Reg%Tr(it)%name) == trim(tr_name)) then
41180 get_tracer_index = it
41190 exit
4120 endif
41210 it = it + 1
4122 enddo
41230end function get_tracer_index
4124
4125!> Allocate segment data fields
41260subroutine allocate_OBC_segment_data(OBC, segment)
4127 type(ocean_OBC_type), intent(in) :: OBC !< Open boundary structure
4128 type(OBC_segment_type), intent(inout) :: segment !< Open boundary segment
4129 ! Local variables
4130 integer :: isd, ied, jsd, jed
4131 integer :: IsdB, IedB, JsdB, JedB
4132 integer :: IscB, IecB, JscB, JecB
4133
41340 isd = segment%HI%isd ; ied = segment%HI%ied
41350 jsd = segment%HI%jsd ; jed = segment%HI%jed
41360 IsdB = segment%HI%IsdB ; IedB = segment%HI%IedB
41370 JsdB = segment%HI%JsdB ; JedB = segment%HI%JedB
41380 IscB = segment%HI%IscB ; IecB = segment%HI%IecB
41390 JscB = segment%HI%JscB ; JecB = segment%HI%JecB
4140
41410 if (.not. segment%on_pe) return
4142
41430 if (segment%is_E_or_W) then
4144 ! If these are just Flather, change update_OBC_segment_data accordingly
41450 allocate(segment%Htot(IsdB:IedB,jsd:jed), source=0.0)
4146 ! Allocate dZtot with extra values at the end to avoid segmentation faults in cases where
4147 ! it is interpolated to OBC vorticity points.
41480 allocate(segment%dZtot(IsdB:IedB,jsd-1:jed+1), source=0.0)
41490 allocate(segment%SSH(IsdB:IedB,jsd:jed), source=0.0)
41500 allocate(segment%tidal_elev(IsdB:IedB,jsd:jed), source=0.0)
41510 if (segment%radiation) &
41520 allocate(segment%rx_norm_rad(IsdB:IedB,jsd:jed,OBC%ke), source=0.0)
41530 allocate(segment%normal_vel(IsdB:IedB,jsd:jed,OBC%ke), source=0.0)
41540 allocate(segment%normal_vel_bt(IsdB:IedB,jsd:jed), source=0.0)
41550 allocate(segment%normal_trans(IsdB:IedB,jsd:jed,OBC%ke), source=0.0)
41560 allocate(segment%normal_trans_bt(IsdB:IedB,jsd:jed), source=0.0)
41570 allocate(segment%tidal_vn(IsdB:IedB,jsd:jed), source=0.0)
41580 if (segment%nudged) &
41590 allocate(segment%nudged_normal_vel(IsdB:IedB,jsd:jed,OBC%ke), source=0.0)
4160 if (segment%radiation_tan .or. segment%nudged_tan .or. &
4161 segment%specified_tan .or. segment%oblique_tan .or. &
41620 (OBC%vorticity_config == OBC_VORTICITY_COMPUTED) .or. &
4163 (OBC%strain_config == OBC_STRAIN_COMPUTED)) then
41640 allocate(segment%tangential_vel(IsdB:IedB,JsdB:JedB,OBC%ke), source=0.0)
41650 allocate(segment%tidal_vt(IsdB:IedB,JsdB:JedB), source=0.0)
4166 endif
41670 if (segment%nudged_tan) &
41680 allocate(segment%nudged_tangential_vel(IsdB:IedB,JsdB:JedB,OBC%ke), source=0.0)
41690 if (segment%nudged_grad) &
41700 allocate(segment%nudged_tangential_grad(IsdB:IedB,JsdB:JedB,OBC%ke), source=0.0)
4171 if (segment%radiation_grad .or. segment%oblique_grad .or. segment%specified_grad .or. &
41720 (OBC%vorticity_config == OBC_VORTICITY_SPECIFIED) .or. &
4173 (OBC%strain_config == OBC_STRAIN_SPECIFIED)) &
41740 allocate(segment%tangential_grad(IsdB:IedB,JsdB:JedB,OBC%ke), source=0.0)
41750 if (segment%oblique) then
41760 allocate(segment%grad_normal(JsdB:JedB,2,OBC%ke), source=0.0)
41770 allocate(segment%rx_norm_obl(IsdB:IedB,jsd:jed,OBC%ke), source=0.0)
41780 allocate(segment%ry_norm_obl(IsdB:IedB,jsd:jed,OBC%ke), source=0.0)
41790 allocate(segment%cff_normal(IsdB:IedB,jsd:jed,OBC%ke), source=0.0)
4180 endif
41810 if (segment%oblique_tan) &
41820 allocate(segment%grad_tan(jsd-1:jed+1,2,OBC%ke), source=0.0)
41830 if (segment%oblique_grad) &
41840 allocate(segment%grad_gradient(jsd:jed,2,OBC%ke), source=0.0)
4185 endif
4186
41870 if (segment%is_N_or_S) then
4188 ! If these are just Flather, change update_OBC_segment_data accordingly
41890 allocate(segment%Htot(isd:ied,JsdB:JedB), source=0.0)
4190 ! Allocate dZtot with extra values at the end to avoid segmentation faults in cases where
4191 ! it is interpolated to OBC vorticity points.
41920 allocate(segment%dZtot(isd-1:ied+1,JsdB:JedB), source=0.0)
41930 allocate(segment%SSH(isd:ied,JsdB:JedB), source=0.0)
41940 allocate(segment%tidal_elev(isd:ied,JsdB:JedB), source=0.0)
41950 if (segment%radiation) &
41960 allocate(segment%ry_norm_rad(isd:ied,JsdB:JedB,OBC%ke), source=0.0)
41970 allocate(segment%normal_vel(isd:ied,JsdB:JedB,OBC%ke), source=0.0)
41980 allocate(segment%normal_vel_bt(isd:ied,JsdB:JedB), source=0.0)
41990 allocate(segment%normal_trans(isd:ied,JsdB:JedB,OBC%ke), source=0.0)
42000 allocate(segment%normal_trans_bt(isd:ied,JsdB:JedB), source=0.0)
42010 allocate(segment%tidal_vn(isd:ied,JsdB:JedB), source=0.0)
42020 if (segment%nudged) &
42030 allocate(segment%nudged_normal_vel(isd:ied,JsdB:JedB,OBC%ke), source=0.0)
4204 if (segment%radiation_tan .or. segment%nudged_tan .or. &
4205 segment%specified_tan .or. segment%oblique_tan .or. &
42060 (OBC%vorticity_config == OBC_VORTICITY_COMPUTED) .or. &
4207 (OBC%strain_config == OBC_STRAIN_COMPUTED)) then
42080 allocate(segment%tangential_vel(IsdB:IedB,JsdB:JedB,OBC%ke), source=0.0)
42090 allocate(segment%tidal_vt(IsdB:IedB,JsdB:JedB), source=0.0)
4210 endif
42110 if (segment%nudged_tan) &
42120 allocate(segment%nudged_tangential_vel(IsdB:IedB,JsdB:JedB,OBC%ke), source=0.0)
42130 if (segment%nudged_grad) &
42140 allocate(segment%nudged_tangential_grad(IsdB:IedB,JsdB:JedB,OBC%ke), source=0.0)
4215 if (segment%radiation_grad .or. segment%oblique_grad .or. segment%specified_grad .or. &
42160 (OBC%vorticity_config == OBC_VORTICITY_SPECIFIED) .or. &
4217 (OBC%strain_config == OBC_STRAIN_SPECIFIED)) &
42180 allocate(segment%tangential_grad(IsdB:IedB,JsdB:JedB,OBC%ke), source=0.0)
42190 if (segment%oblique) then
42200 allocate(segment%grad_normal(IsdB:IedB,2,OBC%ke), source=0.0)
42210 allocate(segment%rx_norm_obl(isd:ied,JsdB:JedB,OBC%ke), source=0.0)
42220 allocate(segment%ry_norm_obl(isd:ied,JsdB:JedB,OBC%ke), source=0.0)
42230 allocate(segment%cff_normal(isd:ied,JsdB:JedB,OBC%ke), source=0.0)
4224 endif
42250 if (segment%oblique_tan) &
42260 allocate(segment%grad_tan(isd-1:ied+1,2,OBC%ke), source=0.0)
42270 if (segment%oblique_grad) &
42280 allocate(segment%grad_gradient(isd:ied,2,OBC%ke), source=0.0)
4229 endif
4230
4231end subroutine allocate_OBC_segment_data
4232
4233!> Deallocate segment data fields
42340subroutine deallocate_OBC_segment_data(segment)
4235 type(OBC_segment_type), intent(inout) :: segment !< Open boundary segment
4236
42370 if (.not. segment%on_pe) return
4238
42390 if (allocated(segment%Htot)) deallocate(segment%Htot)
42400 if (allocated(segment%dZtot)) deallocate(segment%dZtot)
42410 if (allocated(segment%SSH)) deallocate(segment%SSH)
42420 if (allocated(segment%tidal_elev)) deallocate(segment%tidal_elev)
42430 if (allocated(segment%rx_norm_rad)) deallocate(segment%rx_norm_rad)
42440 if (allocated(segment%ry_norm_rad)) deallocate(segment%ry_norm_rad)
42450 if (allocated(segment%rx_norm_obl)) deallocate(segment%rx_norm_obl)
42460 if (allocated(segment%ry_norm_obl)) deallocate(segment%ry_norm_obl)
42470 if (allocated(segment%cff_normal)) deallocate(segment%cff_normal)
42480 if (allocated(segment%grad_normal)) deallocate(segment%grad_normal)
42490 if (allocated(segment%grad_tan)) deallocate(segment%grad_tan)
42500 if (allocated(segment%grad_gradient)) deallocate(segment%grad_gradient)
42510 if (allocated(segment%normal_vel)) deallocate(segment%normal_vel)
42520 if (allocated(segment%normal_vel_bt)) deallocate(segment%normal_vel_bt)
42530 if (allocated(segment%normal_trans)) deallocate(segment%normal_trans)
42540 if (allocated(segment%normal_trans_bt)) deallocate(segment%normal_trans_Bt)
42550 if (allocated(segment%tidal_vn)) deallocate(segment%tidal_vn)
42560 if (allocated(segment%tidal_vt)) deallocate(segment%tidal_vt)
42570 if (allocated(segment%nudged_normal_vel)) deallocate(segment%nudged_normal_vel)
42580 if (allocated(segment%tangential_vel)) deallocate(segment%tangential_vel)
42590 if (allocated(segment%nudged_tangential_vel)) deallocate(segment%nudged_tangential_vel)
42600 if (allocated(segment%nudged_tangential_grad)) deallocate(segment%nudged_tangential_grad)
42610 if (allocated(segment%tangential_grad)) deallocate(segment%tangential_grad)
4262
42630 if (associated(segment%tr_Reg)) call segment_tracer_registry_end(segment%tr_Reg)
42640 if (associated(segment%h_Reg)) call segment_thickness_registry_end(segment%h_Reg)
4265
4266end subroutine deallocate_OBC_segment_data
4267
4268!> Set tangential velocities outside of open boundaries to silly values
4269!! (used for checking the interior state is independent of values outside
4270!! of the domain).
42710subroutine open_boundary_test_extern_uv(G, GV, OBC, u, v)
4272 type(ocean_grid_type), intent(in) :: G !< Ocean grid structure
4273 type(verticalGrid_type), intent(in) :: GV !< The ocean's vertical grid structure
4274 type(ocean_OBC_type), pointer :: OBC !< Open boundary structure
4275 real, dimension(SZIB_(G),SZJ_(G),SZK_(GV)),intent(inout) :: u !< Zonal velocity [L T-1 ~> m s-1]
4276 real, dimension(SZI_(G),SZJB_(G),SZK_(GV)),intent(inout) :: v !< Meridional velocity [L T-1 ~> m s-1]
4277 ! Local variables
4278 integer :: i, j, k, n
4279
42800 if (.not. associated(OBC)) return
4281
42820 do n=1,OBC%number_of_segments
42830 do k = 1, GV%ke
42840 if (OBC%segment(n)%is_N_or_S) then
42850 J = OBC%segment(n)%HI%JsdB
42860 if (OBC%segment(n)%direction == OBC_DIRECTION_N) then
42870 do I = OBC%segment(n)%HI%IsdB, OBC%segment(n)%HI%IedB
42880 u(I,j+1,k) = OBC%silly_u
4289 enddo
4290 else
42910 do I = OBC%segment(n)%HI%IsdB, OBC%segment(n)%HI%IedB
42920 u(I,j,k) = OBC%silly_u
4293 enddo
4294 endif
42950 elseif (OBC%segment(n)%is_E_or_W) then
42960 I = OBC%segment(n)%HI%IsdB
42970 if (OBC%segment(n)%direction == OBC_DIRECTION_E) then
42980 do J = OBC%segment(n)%HI%JsdB, OBC%segment(n)%HI%JedB
42990 v(i+1,J,k) = OBC%silly_u
4300 enddo
4301 else
43020 do J = OBC%segment(n)%HI%JsdB, OBC%segment(n)%HI%JedB
43030 v(i,J,k) = OBC%silly_u
4304 enddo
4305 endif
4306 endif
4307 enddo
4308 enddo
4309
4310end subroutine open_boundary_test_extern_uv
4311
4312!> Set thicknesses outside of open boundaries to silly values
4313!! (used for checking the interior state is independent of values outside
4314!! of the domain).
43150subroutine open_boundary_test_extern_h(G, GV, OBC, h)
4316 type(ocean_grid_type), intent(in) :: G !< Ocean grid structure
4317 type(verticalGrid_type), intent(in) :: GV !< Ocean vertical grid structure
4318 type(ocean_OBC_type), pointer :: OBC !< Open boundary structure
4319 real, dimension(SZI_(G),SZJ_(G), SZK_(GV)),intent(inout) :: h !< Layer thickness [H ~> m or kg m-2]
4320 ! Local variables
4321 real :: silly_h ! A silly thickness for testing [H ~> m or kg m-2]
4322 integer :: i, j, k, n
4323
43240 if (.not. associated(OBC)) return
4325
43260 silly_h = GV%Z_to_H * OBC%silly_h ! This rescaling is here because GV was initialized after OBC.
4327
43280 do n=1,OBC%number_of_segments
43290 do k = 1, GV%ke
43300 if (OBC%segment(n)%is_N_or_S) then
43310 J = OBC%segment(n)%HI%JsdB
43320 if (OBC%segment(n)%direction == OBC_DIRECTION_N) then
43330 do i = OBC%segment(n)%HI%isd, OBC%segment(n)%HI%ied
43340 h(i,j+1,k) = silly_h
4335 enddo
4336 else
43370 do i = OBC%segment(n)%HI%isd, OBC%segment(n)%HI%ied
43380 h(i,j,k) = silly_h
4339 enddo
4340 endif
43410 elseif (OBC%segment(n)%is_E_or_W) then
43420 I = OBC%segment(n)%HI%IsdB
43430 if (OBC%segment(n)%direction == OBC_DIRECTION_E) then
43440 do j = OBC%segment(n)%HI%jsd, OBC%segment(n)%HI%jed
43450 h(i+1,j,k) = silly_h
4346 enddo
4347 else
43480 do j = OBC%segment(n)%HI%jsd, OBC%segment(n)%HI%jed
43490 h(i,j,k) = silly_h
4350 enddo
4351 endif
4352 endif
4353 enddo
4354 enddo
4355
4356end subroutine open_boundary_test_extern_h
4357
4358!> Read OBC values on the segments from files
43590subroutine read_OBC_segment_data(G, GV, US, OBC, tv, h, Time)
4360 type(ocean_grid_type), intent(in) :: G !< Ocean grid structure
4361 type(verticalGrid_type), intent(in) :: GV !< Ocean vertical grid structure
4362 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
4363 type(ocean_OBC_type), pointer :: OBC !< Open boundary structure
4364 type(thermo_var_ptrs), intent(in) :: tv !< Thermodynamics structure
4365 real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(in) :: h !< Thickness [H ~> m or kg m-2]
4366 type(time_type), intent(in) :: Time !< Model time
4367
4368 ! Local variables
4369 integer :: i, j, k, n, m
4370 integer :: isd, ied, jsd, jed, IsdB, IedB, JsdB, JedB
4371 type(OBC_segment_type), pointer :: segment => NULL()
4372 real, dimension(:,:,:), pointer :: tmp_buffer_in => NULL() ! Unrotated input [various units]
4373 integer :: ni_seg, nj_seg ! number of src gridpoints along the segments
4374 integer :: ni_buf, nj_buf ! Number of filled values in tmp_buffer
43750 real :: dz(SZI_(G),SZJ_(G),SZK_(GV)) ! Distance between the interfaces around a layer [Z ~> m]
43760 real, dimension(:,:,:), allocatable, target :: tmp_buffer ! A buffer for input data [various units]
43770 real :: dz_stack(SZK_(GV)) ! Distance between the interfaces at corner points [Z ~> m]
4378 integer :: i_seg_offset, j_seg_offset, bug_offset
4379 real :: net_dz_src ! Total vertical extent of the incoming flow in the source field [Z ~> m]
4380 real :: net_dz_int ! Total vertical extent of the incoming flow in the model [Z ~> m]
4381 real :: scl_fac ! A scaling factor to compensate for differences in total thicknesses [nondim]
4382 integer :: turns ! Number of index quarter turns
4383 logical :: flip_buffer ! If true, the input buffer needs to be transposed
4384
43850 if (.not. associated(OBC)) return
43860 if (OBC%user_BCs_set_globally) return
4387
43880 turns = modulo(G%HI%turns, 4)
43890 dz(:,:,:) = 0.0
43900 call thickness_to_dz(h, tv, dz, G, GV, US)
43910 call pass_var(dz, G%Domain)
4392
43930 do n=1,OBC%number_of_segments
43940 segment => OBC%segment(n)
4395
43960 if (.not. segment%on_pe) cycle ! continue to next segment if not in data domain
4397
43980 isd = segment%HI%isd ; ied = segment%HI%ied ; IsdB = segment%HI%IsdB ; IedB = segment%HI%IedB
43990 jsd = segment%HI%jsd ; jed = segment%HI%jed ; JsdB = segment%HI%JsdB ; JedB = segment%HI%JedB
4400
44010 ni_seg = segment%ie_obc - segment%is_obc + 1 ! Global number of q points
44020 nj_seg = segment%je_obc - segment%js_obc + 1 ! Global number of q points
44030 i_seg_offset = G%idg_offset - segment%HI%IsgB
44040 j_seg_offset = G%jdg_offset - segment%HI%JsgB
4405
4406 ! Calculate auxiliary fields at staggered locations
44070 segment%dZtot(:,:) = 0.0
44080 if (segment%is_E_or_W) then
44090 I = IsdB
4410 ! dZtot may extend one point past the end of the segment on the current PE for use at vorticity points
44110 do k = 1, GV%ke ; do j = max(jsd-1, G%jsd), min(jed+1, G%jed)
44120 segment%dZtot(I,j) = segment%dZtot(I,j) + dz(isd,j,k)
4413 enddo ; enddo
4414 else ! (segment%direction == OBC_DIRECTION_N .or. segment%direction == OBC_DIRECTION_S)
44150 J = JsdB
4416 ! dZtot may extend one point past the end of the segment on the current PE for use at vorticity points
44170 do k = 1, GV%ke ; do i = max(isd-1, G%isd), min(ied+1, G%ied)
44180 segment%dZtot(i,J) = segment%dZtot(i,J) + dz(i,jsd,k)
4419 enddo ; enddo
4420 endif
4421
4422 ! Read data from files to buffer_src
44230 do m=1,segment%num_fields
44240 if (segment%field(m)%required .and. (.not. allocated(segment%field(m)%buffer_dst))) &
44250 call MOM_error(FATAL, 'buffer_dst not allocated')
4426
44270 if ( (.not. segment%field(m)%use_IO) .or. & ! .and. (.not. segment%field(m)%required)
4428 (segment%field(m)%bgc_tracer .and. (.not. OBC%update_OBC_seg_data)) ) &
4429 !This field may not require a high frequency OBC segment update and might be allowed
4430 !a less frequent update as set by the parameter update_OBC_period_max in MOM.F90.
4431 !Cycle if it is not the time to update OBC segment data for this field.
44320 cycle
4433
4434 ! read source data interpolated to the current model time
4435 ! NOTE: buffer is sized for vertex points, but may be used for faces
44360 if (segment%is_E_or_W) then
44370 if (OBC%brushcutter_mode) then
44380 allocate(tmp_buffer(1,nj_seg*2-1,segment%field(m)%nk_src)) ! segment data is currently on supergrid
4439 else
44400 allocate(tmp_buffer(1,nj_seg,segment%field(m)%nk_src)) ! segment data is currently on native grid
4441 endif
4442 else
44430 if (OBC%brushcutter_mode) then
44440 allocate(tmp_buffer(ni_seg*2-1,1,segment%field(m)%nk_src)) ! segment data is currently on supergrid
4445 else
44460 allocate(tmp_buffer(ni_seg,1,segment%field(m)%nk_src)) ! segment data is currently on native grid
4447 endif
4448 endif
4449
4450 ! TODO: Since we conditionally rotate a subset of tmp_buffer_in after
4451 ! reading the value, it is currently not possible to use the rotated
4452 ! implementation of time_interp_extern.
4453 ! For now, we must explicitly allocate and rotate this array.
44540 if (turns /= 0) then
44550 if (modulo(turns, 2) /= 0) then
44560 allocate(tmp_buffer_in(size(tmp_buffer, 2), size(tmp_buffer, 1), size(tmp_buffer, 3)))
4457 else
44580 allocate(tmp_buffer_in(size(tmp_buffer, 1), size(tmp_buffer, 2), size(tmp_buffer, 3)))
4459 endif
4460 else
44610 tmp_buffer_in => tmp_buffer
4462 endif
4463
4464 ! This is where the data values are actually read in.
44650 call time_interp_external(segment%field(m)%handle, Time, tmp_buffer_in, scale=segment%field(m)%scale)
4466
4467 ! NOTE: Rotation of face-points require that we skip the final value when not in brushcutter mode.
44680 if (turns /= 0) then
44690 flip_buffer = ((turns==1) .or. (turns==3))
44700 if (OBC%brushcutter_mode .or. (.not.flip_buffer)) then
44710 call rotate_array(tmp_buffer_in, turns, tmp_buffer)
44720 elseif (flip_buffer .and. segment%is_E_or_W .and. segment%field(m)%on_face) then
44730 nj_buf = size(tmp_buffer, 2) - 1
44740 call rotate_array(tmp_buffer_in(:nj_buf,:,:), turns, tmp_buffer(:,:nj_buf,:))
44750 elseif (flip_buffer .and. segment%is_N_or_S .and. segment%field(m)%on_face) then
44760 ni_buf = size(tmp_buffer, 1) - 1
44770 call rotate_array(tmp_buffer_in(:,:ni_buf,:), turns, tmp_buffer(:ni_buf,:,:))
4478 else
44790 call rotate_array(tmp_buffer_in, turns, tmp_buffer)
4480 endif
4481
4482 if (((segment%field(m)%name == 'U') .and. ((turns==1).or.(turns==2))) .or. &
4483 ((segment%field(m)%name == 'V') .and. ((turns==2).or.(turns==3))) .or. &
4484 ((segment%field(m)%name == 'Vamp') .and. ((turns==2).or.(turns==3))) .or. &
4485 ((segment%field(m)%name == 'Uamp') .and. ((turns==1).or.(turns==2))) .or. &
44860 ((segment%field(m)%name == 'DVDX') .and. ((turns==1).or.(turns==3))) .or. &
4487 ((segment%field(m)%name == 'DUDY') .and. ((turns==1).or.(turns==3))) ) then
44880 tmp_buffer(:,:,:) = -tmp_buffer(:,:,:)
4489 endif
4490 endif
4491
44920 if (OBC%brushcutter_mode) then
4493 ! In brushcutter mode, the input data includes vales at both the vorticity point nodes and
4494 ! the velocity point faces of the OBC segments. The vorticity node values are at the odd
4495 ! positions in tmp_buffer, while the faces are at the even points. The bug that is being
4496 ! corrected here is the use of the odd indexed points for both the corners and the faces.
44970 bug_offset = 0 ; if (OBC%hor_index_bug) bug_offset = -1
44980 if (segment%is_E_or_W) then
44990 if (.not.segment%field(m)%on_face) then
4500 segment%field(m)%buffer_src(IsdB,:,:) = &
45010 tmp_buffer(1, 2*(JsdB+j_seg_offset+1)-1:2*(JedB+j_seg_offset)+1:2, :)
4502 else
4503 segment%field(m)%buffer_src(IsdB,:,:) = &
45040 tmp_buffer(1, 2*(JsdB+j_seg_offset+1)+bug_offset:2*(JedB+j_seg_offset):2, :)
4505 endif
4506 else
45070 if (.not.segment%field(m)%on_face) then
4508 segment%field(m)%buffer_src(:,JsdB,:) = &
45090 tmp_buffer(2*(IsdB+i_seg_offset+1)-1:2*(IedB+i_seg_offset)+1:2, 1, :)
4510 else
4511 segment%field(m)%buffer_src(:,JsdB,:) = &
45120 tmp_buffer(2*(IsdB+i_seg_offset+1)+bug_offset:2*(IedB+i_seg_offset):2, 1, :)
4513 endif
4514 endif
4515 else ! Not brushcutter_mode.
45160 if (segment%is_E_or_W) then
45170 if (.not.segment%field(m)%on_face) then
4518 segment%field(m)%buffer_src(IsdB,:,:) = &
45190 tmp_buffer(1,JsdB+j_seg_offset+1:JedB+j_seg_offset+1,:)
4520 else
4521 segment%field(m)%buffer_src(IsdB,:,:) = &
45220 tmp_buffer(1,JsdB+j_seg_offset+1:JedB+j_seg_offset,:)
4523 endif
4524 else
45250 if (.not.segment%field(m)%on_face) then
4526 segment%field(m)%buffer_src(:,JsdB,:) = &
45270 tmp_buffer(IsdB+i_seg_offset+1:IedB+i_seg_offset+1,1,:)
4528 else
4529 segment%field(m)%buffer_src(:,JsdB,:) = &
45300 tmp_buffer(IsdB+i_seg_offset+1:IedB+i_seg_offset,1,:)
4531 endif
4532 endif
4533 endif
4534
4535 ! no dz for tidal variables
45360 if (segment%field(m)%nk_src <= 1) then ! This is 2-d data with no remapping.
45370 segment%field(m)%buffer_dst(:,:,1) = segment%field(m)%buffer_src(:,:,1)
45380 elseif (field_is_tidal(segment%field(m)%name)) then
4539 ! The 3rd axis for tidal variables is the tidal constituent, so there is no remapping.
45400 segment%field(m)%buffer_dst(:,:,:) = segment%field(m)%buffer_src(:,:,:)
4541 else
4542 ! Read in 3-d data that may need to be remapped onto the new grid
4543 ! This is also where the 2-d tidal data values (apart from phase and amp) are actually read in.
45440 call time_interp_external(segment%field(m)%dz_handle, Time, tmp_buffer_in, scale=US%m_to_Z)
4545
45460 if (turns /= 0) then
45470 flip_buffer = ((turns==1) .or. (turns==3))
45480 if (flip_buffer .and. segment%is_E_or_W .and. segment%field(m)%on_face) then
45490 nj_buf = size(tmp_buffer, 2) - 1
45500 call rotate_array(tmp_buffer_in(:nj_buf,:,:), turns, tmp_buffer(:,:nj_buf,:))
45510 elseif (flip_buffer .and. segment%is_N_or_S .and. segment%field(m)%on_face) then
45520 ni_buf = size(tmp_buffer, 1) - 1
45530 call rotate_array(tmp_buffer_in(:,:ni_buf,:), turns, tmp_buffer(:ni_buf,:,:))
4554 else
45550 call rotate_array(tmp_buffer_in, turns, tmp_buffer)
4556 endif
4557 endif ! End of rotation
4558
45590 if (OBC%brushcutter_mode) then
45600 bug_offset = 0 ; if (OBC%hor_index_bug) bug_offset = -1
45610 if (segment%is_E_or_W) then
45620 if (.not.segment%field(m)%on_face) then
4563 segment%field(m)%dz_src(IsdB,:,:) = &
45640 tmp_buffer(1, 2*(JsdB+j_seg_offset+1)-1:2*(JedB+j_seg_offset)+1:2, :)
4565 else
4566 segment%field(m)%dz_src(IsdB,:,:) = &
45670 tmp_buffer(1, 2*(JsdB+j_seg_offset+1)+bug_offset:2*(JedB+j_seg_offset):2, :)
4568 endif
4569 else
45700 if (.not.segment%field(m)%on_face) then
4571 segment%field(m)%dz_src(:,JsdB,:) = &
45720 tmp_buffer(2*(IsdB+i_seg_offset+1)-1:2*(IedB+i_seg_offset)+1:2, 1, :)
4573 else
4574 segment%field(m)%dz_src(:,JsdB,:) = &
45750 tmp_buffer(2*(IsdB+i_seg_offset+1)+bug_offset:2*(IedB+i_seg_offset):2, 1, :)
4576 endif
4577 endif
4578 else ! Not brushcutter_mode.
45790 if (segment%is_E_or_W) then
45800 if (.not.segment%field(m)%on_face) then
4581 segment%field(m)%dz_src(IsdB,:,:) = &
45820 tmp_buffer(1,JsdB+j_seg_offset+1:JedB+j_seg_offset+1,:)
4583 else
4584 segment%field(m)%dz_src(IsdB,:,:) = &
45850 tmp_buffer(1,JsdB+j_seg_offset+1:JedB+j_seg_offset,:)
4586 endif
4587 else
45880 if (.not.segment%field(m)%on_face) then
4589 segment%field(m)%dz_src(:,JsdB,:) = &
45900 tmp_buffer(IsdB+i_seg_offset+1:IedB+i_seg_offset+1,1,:)
4591 else
4592 segment%field(m)%dz_src(:,JsdB,:) = &
45930 tmp_buffer(IsdB+i_seg_offset+1:IedB+i_seg_offset,1,:)
4594 endif
4595 endif
4596 endif
4597
45980 if ((.not.segment%field(m)%on_face) .and. (.not.OBC%hor_index_bug)) then
4599 ! This point is at the OBC vorticity point nodes, rather than the OBC velocity point faces.
46000 call adjustSegmentEtaToFitBathymetry(G, GV, US, segment, m, at_node=.true.)
4601 else
46020 call adjustSegmentEtaToFitBathymetry(G, GV, US, segment, m, at_node=.false.)
4603 endif
4604
46050 if (segment%is_E_or_W) then
46060 I = IsdB
46070 if (.not.segment%field(m)%on_face) then
4608 ! Do q points for the whole segment
46090 do J = max(JsdB, G%jsd), min(JedB, G%jed-1)
4610 ! Using the h remapping approach
4611 ! Pretty sure we need to check for source/target grid consistency here
4612 !### For a concave corner between OBC segments, there are 3 thicknesses we might
4613 ! consider using.
46140 segment%field(m)%buffer_dst(I,J,:) = 0.0 ! initialize remap destination buffer
46150 if ((G%mask2dCu(I,j) > 0.0) .or. (G%mask2dCu(I,j+1) > 0.0)) then
4616 dz_stack(:) = (1.0 / (G%mask2dCu(I,j) + G%mask2dCu(I,j+1))) * &
46170 (G%mask2dCu(I,j) * dz(isd,j,:) + G%mask2dCu(I,j+1) * dz(isd,j+1,:))
4618 call remapping_core_h(OBC%remap_z_CS, &
4619 segment%field(m)%nk_src, segment%field(m)%dz_src(I,J,:), &
4620 segment%field(m)%buffer_src(I,J,:), &
46210 GV%ke, dz_stack, segment%field(m)%buffer_dst(I,J,:))
4622 endif
4623 enddo
4624 else
46250 do j = JsdB+1, JedB
4626 ! Using the h remapping approach
4627 ! Pretty sure we need to check for source/target grid consistency here
46280 segment%field(m)%buffer_dst(I,j,:) = 0.0 ! initialize remap destination buffer
46290 if (G%mask2dCu(I,j)>0.) then
46300 net_dz_src = sum( segment%field(m)%dz_src(I,j,:) )
46310 net_dz_int = sum( dz(isd,j,:) )
46320 scl_fac = net_dz_int / net_dz_src
4633 call remapping_core_h(OBC%remap_z_CS, &
4634 segment%field(m)%nk_src, scl_fac*segment%field(m)%dz_src(I,j,:), &
4635 segment%field(m)%buffer_src(I,j,:), &
46360 GV%ke, dz(isd,j,:), segment%field(m)%buffer_dst(I,j,:))
4637 endif
4638 enddo
4639 endif
4640 else
46410 J = JsdB
46420 if (.not.segment%field(m)%on_face) then
4643 ! Do q points for the whole segment
46440 do I = max(IsdB, G%isd), min(IedB, G%ied-1)
46450 segment%field(m)%buffer_dst(I,J,:) = 0.0 ! initialize remap destination buffer
46460 if ((G%mask2dCv(i,J) > 0.0) .or. (G%mask2dCv(i+1,J) > 0.0)) then
4647 ! Using the h remapping approach
4648 ! Pretty sure we need to check for source/target grid consistency here
4649 dz_stack(:) = (1.0 / (G%mask2dCv(i,J) + G%mask2dCv(i+1,J))) * &
46500 (G%mask2dCv(i,J) * dz(i,jsd,:) + G%mask2dCv(i+1,J) * dz(i+1,jsd,:))
4651 call remapping_core_h(OBC%remap_z_CS, &
4652 segment%field(m)%nk_src, segment%field(m)%dz_src(I,J,:), &
4653 segment%field(m)%buffer_src(I,J,:), &
46540 GV%ke, dz_stack, segment%field(m)%buffer_dst(I,J,:))
4655 endif
4656 enddo
4657 else
46580 do i = IsdB+1, IedB
4659 ! Using the h remapping approach
4660 ! Pretty sure we need to check for source/target grid consistency here
46610 segment%field(m)%buffer_dst(i,J,:) = 0.0 ! initialize remap destination buffer
46620 if (G%mask2dCv(i,J)>0.) then
46630 net_dz_src = sum( segment%field(m)%dz_src(i,J,:) )
46640 net_dz_int = sum( dz(i,jsd,:) )
46650 scl_fac = net_dz_int / net_dz_src
4666 call remapping_core_h(OBC%remap_z_CS, &
4667 segment%field(m)%nk_src, scl_fac* segment%field(m)%dz_src(i,J,:), &
4668 segment%field(m)%buffer_src(i,J,:), &
46690 GV%ke, dz(i,jsd,:), segment%field(m)%buffer_dst(i,J,:))
4670 endif
4671 enddo
4672 endif
4673 endif
4674 endif
46750 deallocate(tmp_buffer)
46760 if (turns /= 0) deallocate(tmp_buffer_in)
4677 enddo ! end field loop
4678 enddo ! endd segment loop
46790end subroutine read_OBC_segment_data
4680
4681!> Update OBC segment velocities, gradient, SSH and the external fields %t of thickness/tracer reservoirs.
46820subroutine update_OBC_segment_data(G, GV, US, OBC, h, Time)
4683 type(ocean_grid_type), intent(in) :: G !< Ocean grid structure
4684 type(verticalGrid_type), intent(in) :: GV !< Ocean vertical grid structure
4685 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
4686 type(ocean_OBC_type), pointer :: OBC !< Open boundary structure
4687 real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(in) :: h !< Thickness [H ~> m or kg m-2]
4688 type(time_type), intent(in) :: Time !< Model time
4689
4690 ! Local variables
4691 type(OBC_segment_type), pointer :: segment => NULL()
4692 integer :: c, i, j, k, n, m, nz, nt
4693 integer :: isd, ied, jsd, jed, IsdB, IedB, JsdB, JedB
4694 integer :: is_seg, ie_seg, js_seg, je_seg ! Orientation-agnostic loop ranges
4695 integer :: i_offset_in, j_offset_in ! Indexing offset for interior cells
4696 integer :: F_G, F_VN, F_VNAMP, F_VNPHASE, F_VT, F_VTAMP, F_VTPHASE ! Field indices
4697 real :: ramp_value ! If OBC%ramp is True, where we are on the ramp from 0 to 1, or 1 otherwise [nondim].
4698 real :: time_delta ! Time since tidal reference date [T ~> s]
4699 real :: tidal_amp, tidal_phase ! Tidal amplitude [Z ~> m] and phase [rad]
4700
47010 if (.not. associated(OBC)) return
47020 if (OBC%user_BCs_set_globally) return
4703
47040 nz = GV%ke
4705
47060 if (OBC%add_tide_constituents) &
47070 time_delta = time_minus_signed(Time, OBC%time_ref, scale=US%s_to_T)
4708
47090 do n=1,OBC%number_of_segments
47100 segment => OBC%segment(n)
4711
47120 if (.not. segment%on_pe) cycle ! continue to next segment if not in data domain
4713
4714 ! Segment indices are on q points:
4715 ! | x | x | x | x | jsd/jed (if southern boundary)
4716 ! |-----------|-----------|-----------|-----------| JsdB/JedB
4717 ! IsdB isd ied IedB
4718 ! | x | x | x | x | jsd/jed (if northern boundary)
4719
47200 isd = segment%HI%isd ; ied = segment%HI%ied ; IsdB = segment%HI%IsdB ; IedB = segment%HI%IedB
47210 jsd = segment%HI%jsd ; jed = segment%HI%jed ; JsdB = segment%HI%JsdB ; JedB = segment%HI%JedB
47220 i_offset_in = ied - IedB ! = 0 if East, South, North; = 1 if West
47230 j_offset_in = jed - JedB ! = 0 if North, West, East ; = 1 if South
4724
47250 if (segment%is_E_or_W) then
47260 is_seg = IsdB ; ie_seg = is_seg
47270 js_seg = jsd ; je_seg = jed
47280 F_VN = F_U ; F_VNAMP = F_UAMP ; F_VNPHASE = F_UPHASE
47290 F_VT = F_V ; F_VTAMP = F_VAMP ; F_VTPHASE = F_VPHASE ; F_G = F_VX
4730 else
47310 is_seg = isd ; ie_seg = ied
47320 js_seg = JsdB ; je_seg = js_seg
47330 F_VN = F_V ; F_VNAMP = F_VAMP ; F_VNPHASE = F_VPHASE
47340 F_VT = F_U ; F_VTAMP = F_UAMP ; F_VTPHASE = F_UPHASE ; F_G = F_UY
4735 endif
4736
4737 ! Update normal velocity, transport. Split by orientation for now because of G%dyCu and G%dxCv.
47380 if (allocated(segment%field(F_VN)%buffer_dst)) then
4739 ! Update tidal normal velocity
47400 segment%tidal_vn(:,:) = 0.0
47410 if (OBC%add_tide_constituents) then
47420 do c=1,OBC%n_tide_constituents ; do j=js_seg,je_seg ; do i=is_seg,ie_seg
47430 tidal_amp = OBC%tide_fn(c) * segment%field(F_VNAMP)%buffer_dst(i,j,c)
4744 tidal_phase = (time_delta * OBC%tide_frequencies(c) - segment%field(F_VNPHASE)%buffer_dst(i,j,c)) &
47450 + (OBC%tide_eq_phases(c) + OBC%tide_un(c))
47460 segment%tidal_vn(i,j) = segment%tidal_vn(i,j) + tidal_amp * cos(tidal_phase)
4747 enddo ; enddo ; enddo
4748 endif
4749
47500 segment%Htot(:,:) = 0.0
47510 segment%normal_trans_bt(:,:) = 0.0
47520 if (segment%is_E_or_W) then
47530 do k=1,nz ; do j=js_seg,je_seg ; do i=is_seg,ie_seg
47540 segment%Htot(i,j) = segment%Htot(i,j) + h(i+i_offset_in,j+j_offset_in,k)
47550 segment%normal_vel(i,j,k) = segment%field(F_VN)%buffer_dst(i,j,k) + segment%tidal_vn(i,j)
4756 segment%normal_trans(i,j,k) = &
47570 segment%normal_vel(i,j,k) * h(i+i_offset_in,j+j_offset_in,k) * G%dyCu(i,j)
47580 segment%normal_trans_bt(i,j) = segment%normal_trans_bt(i,j) + segment%normal_trans(i,j,k)
4759 enddo ; enddo ; enddo
47600 do j=js_seg,je_seg ; do i=is_seg,ie_seg
4761 segment%normal_vel_bt(i,j) = segment%normal_trans_bt(i,j) &
47620 / (max(segment%Htot(i,j), 1.e-12 * GV%m_to_H) * G%dyCu(i,j))
4763 enddo ; enddo
4764 else
47650 do k=1,nz ; do j=js_seg,je_seg ; do i=is_seg,ie_seg
47660 segment%Htot(i,j) = segment%Htot(i,j) + h(i+i_offset_in,j+j_offset_in,k)
47670 segment%normal_vel(i,j,k) = segment%field(F_VN)%buffer_dst(i,j,k) + segment%tidal_vn(i,j)
4768 segment%normal_trans(i,j,k) = &
47690 segment%normal_vel(i,j,k) * h(i+i_offset_in,j+j_offset_in,k) * G%dxCv(i,j)
47700 segment%normal_trans_bt(i,j) = segment%normal_trans_bt(i,j) + segment%normal_trans(i,j,k)
4771 enddo ; enddo ; enddo
47720 do j=js_seg,je_seg ; do i=is_seg,ie_seg
4773 segment%normal_vel_bt(i,j) = segment%normal_trans_bt(i,j) &
47740 / (max(segment%Htot(i,j), 1.e-12 * GV%m_to_H) * G%dxCv(i,j))
4775 enddo ; enddo
4776 endif
4777
47780 if (allocated(segment%nudged_normal_vel)) then
47790 do k=1,nz ; do j=js_seg,je_seg ; do i=is_seg,ie_seg
47800 segment%nudged_normal_vel(i,j,k) = segment%normal_vel(i,j,k)
4781 enddo ; enddo ; enddo
4782 endif
4783 endif
4784
4785 ! Update tangential velocity
47860 if (allocated(segment%tangential_vel) .and. allocated(segment%field(F_VT)%buffer_dst)) then
4787 ! Update tidal tangential velocity
47880 segment%tidal_vt(:,:) = 0.0
47890 if (OBC%add_tide_constituents) then
47900 do c=1,OBC%n_tide_constituents ; do J=JsdB,JedB ; do I=IsdB,IedB
47910 tidal_amp = OBC%tide_fn(c) * segment%field(F_VTAMP)%buffer_dst(I,J,c)
4792 tidal_phase = (time_delta * OBC%tide_frequencies(c) - segment%field(F_VTPHASE)%buffer_dst(I,J,c)) &
47930 + (OBC%tide_eq_phases(c) + OBC%tide_un(c))
47940 segment%tidal_vt(I,J) = segment%tidal_vt(I,J) + tidal_amp * cos(tidal_phase)
4795 enddo ; enddo ; enddo
4796 endif
4797
47980 do k=1,nz ; do J=JsdB,JedB ; do I=IsdB,IedB
47990 segment%tangential_vel(I,J,k) = segment%field(F_VT)%buffer_dst(I,J,k) + segment%tidal_vt(I,J)
4800 enddo ; enddo ; enddo
4801
48020 if (allocated(segment%nudged_tangential_vel)) then
48030 do k=1,nz ; do J=JsdB,JedB ; do I=IsdB,IedB
48040 segment%nudged_tangential_vel(I,J,k) = segment%tangential_vel(I,J,k)
4805 enddo ; enddo ; enddo
4806 endif
4807 endif
4808
4809 ! Update tangential gradient dvdx and dudy
48100 if (allocated(segment%tangential_grad) .and. allocated(segment%field(F_G)%buffer_dst)) then
48110 do k=1,nz ; do J=JsdB,JedB ; do I=IsdB,IedB
48120 segment%tangential_grad(I,J,k) = segment%field(F_G)%buffer_dst(I,J,k)
4813 enddo ; enddo ; enddo
4814
48150 if (allocated(segment%nudged_tangential_grad)) then
48160 do k=1,nz ; do J=JsdB,JedB ; do I=IsdB,IedB
48170 segment%nudged_tangential_grad(I,J,k) = segment%tangential_grad(I,J,k)
4818 enddo ; enddo ; enddo
4819 endif
4820 endif
4821
4822 ! Update SSH
48230 if (allocated(segment%field(F_Z)%buffer_dst)) then
4824 ! Update tidal SSH
48250 segment%tidal_elev(:,:) = 0.0
48260 if (OBC%add_tide_constituents) then
48270 do c=1,OBC%n_tide_constituents ; do j=js_seg,je_seg ; do i=is_seg,ie_seg
48280 tidal_amp = OBC%tide_fn(c) * segment%field(F_ZAMP)%buffer_dst(i,j,c)
4829 tidal_phase = (time_delta * OBC%tide_frequencies(c) - segment%field(F_ZPHASE)%buffer_dst(i,j,c)) &
48300 + (OBC%tide_eq_phases(c) + OBC%tide_un(c))
48310 segment%tidal_elev(i,j) = segment%tidal_elev(i,j) + tidal_amp * cos(tidal_phase)
4832 enddo ; enddo ; enddo
4833 endif
4834
48350 ramp_value = 1.0 ; if (OBC%ramp) ramp_value = OBC%ramp_value
48360 do j=js_seg,je_seg ; do i=is_seg,ie_seg
48370 segment%SSH(i,j) = ramp_value * (segment%field(F_Z)%buffer_dst(i,j,1) + segment%tidal_elev(i,j))
4838 enddo ; enddo
4839 endif
4840
4841 ! Update thickness registry
48420 if (OBC%thickness_x_reservoirs_used .or. OBC%thickness_y_reservoirs_used) then
48430 do k=1,nz ; do j=js_seg,je_seg ; do i=is_seg,ie_seg
48440 segment%h_Reg%h(i,j,k) = h(i+i_offset_in,j+j_offset_in,k)
4845 enddo ; enddo ; enddo
4846 endif
4847
4848 ! Update tracer registry
48490 do m = NUM_PHYS_FIELDS-1, segment%num_fields ! F_T = NUM_PHYS_FIELDS-1 and F_S = NUM_PHYS_FIELDS
48500 if (.not. allocated(segment%field(m)%buffer_dst) .or. &
4851 (segment%field(m)%bgc_tracer .and. (.not. OBC%update_OBC_seg_data))) then
48520 cycle
4853 endif
48540 nt = segment%field(m)%tr_index
4855 ! Note the following unnecessary IF-branch is kept from the old code (as recent as Jan 2026).
4856 ! In the old code segment%field(m)%buffer_dst is always allocated at this point, and therefore
4857 ! the "else" section is unreachable. This will be fixed when OBC_inflow_conc is reworked.
48580 if (allocated(segment%field(m)%buffer_dst)) then
48590 do k=1,nz ; do j=js_seg,je_seg ; do i=is_seg,ie_seg
48600 segment%tr_Reg%Tr(nt)%t(i,j,k) = segment%field(m)%buffer_dst(i,j,k)
4861 enddo ; enddo ; enddo
4862 else
48630 segment%tr_Reg%Tr(nt)%OBC_inflow_conc = segment%field(m)%value
4864 endif
4865 enddo ! end tracer field loop
4866 enddo ! end segment loop
4867end subroutine update_OBC_segment_data
4868
4869!> Initialize thickness and tracer reservoirs to external value.
48700subroutine initialize_OBC_segment_reservoirs(GV, OBC)
4871 type(verticalGrid_type), intent(in) :: GV !< Ocean vertical grid structure
4872 type(ocean_OBC_type), pointer :: OBC !< Open boundary structure
4873
4874 ! Local variables
4875 type(OBC_segment_type), pointer :: segment => NULL()
4876 integer :: isd, ied, jsd, jed, IsdB, IedB, JsdB, JedB
4877 integer :: is_seg, ie_seg, js_seg, je_seg, nz
4878 integer :: n, m, nt, i, j, k
4879 character(len=256) :: msg ! Error message
4880
48810 if (.not. associated(OBC)) return
4882
48830 nz = GV%ke
4884
48850 do n=1,OBC%number_of_segments
48860 segment => OBC%segment(n)
4887
48880 if (.not. segment%on_pe) cycle
4889
48900 isd = segment%HI%isd ; ied = segment%HI%ied ; IsdB = segment%HI%IsdB ; IedB = segment%HI%IedB
48910 jsd = segment%HI%jsd ; jed = segment%HI%jed ; JsdB = segment%HI%JsdB ; JedB = segment%HI%JedB
4892
48930 if (segment%is_E_or_W) then
48940 is_seg = IsdB ; ie_seg = IedB ! = is_seg
48950 js_seg = jsd ; je_seg = jed
4896 else
48970 is_seg = isd ; ie_seg = ied
48980 js_seg = JsdB ; je_seg = JedB ! = js_seg
4899 endif
4900
4901 ! Thickness
4902 ! If the thickness reservoir has not yet been initialized, then set to external value.
49030 if (OBC%thickness_x_reservoirs_used .or. OBC%thickness_y_reservoirs_used) then
49040 if (.not. segment%h_Reg%is_initialized) then ! h_Reg may be initialized by fill_thickness_segments
49050 do k=1,nz ; do j=js_seg,je_seg ; do i=is_seg,ie_seg
49060 segment%h_Reg%h_res(i,j,k) = segment%h_Reg%h(i,j,k)
4907 enddo ; enddo ; enddo
49080 segment%h_Reg%is_initialized = .true.
4909 endif
4910 endif
4911
4912 ! Tracers
4913 ! If the tracer reservoir has not yet been initialized, then set to external value.
49140 do m=NUM_PHYS_FIELDS-1, segment%num_fields ! F_T = NUM_PHYS_FIELDS-1 and F_S = NUM_PHYS_FIELDS
49150 if (.not. allocated(segment%field(m)%buffer_dst)) cycle
49160 nt = segment%field(m)%tr_index
49170 if (.not. segment%tr_Reg%Tr(nt)%is_initialized) then ! T/S may be initialized by fill_temp_salt_segments
49180 do k=1,nz ; do j=js_seg,je_seg ; do i=is_seg,ie_seg
49190 segment%tr_Reg%Tr(nt)%tres(i,j,k) = segment%tr_Reg%Tr(nt)%t(i,j,k)
4920 enddo ; enddo ; enddo
49210 segment%tr_Reg%Tr(nt)%is_initialized = .true.
4922 endif
4923 enddo ! end tracer field loop
4924 enddo ! end segment loop
4925end subroutine initialize_OBC_segment_reservoirs
4926
4927!> Update the OBC ramp value as a function of time.
4928!! If called with the optional argument activate=.true., record the
4929!! value of Time as the beginning of the ramp period.
49300subroutine update_OBC_ramp(Time, OBC, US, activate)
4931 type(time_type), target, intent(in) :: Time !< Current model time
4932 type(ocean_OBC_type), intent(inout) :: OBC !< Open boundary structure
4933 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
4934 logical, optional, intent(in) :: activate !< Specify whether to record the value of
4935 !! Time as the beginning of the ramp period
4936
4937 ! Local variables
4938 real :: deltaTime ! The time since start of ramping [T ~> s]
4939 real :: wghtA ! A temporary variable used to set OBC%ramp_value [nondim]
4940 character(len=12) :: msg
4941
49420 if (.not. OBC%ramp) return ! This indicates the ramping is turned off
4943
4944 ! We use the optional argument to indicate this Time should be recorded as the
4945 ! beginning of the ramp-up period.
49460 if (present(activate)) then
49470 if (activate) then
49480 OBC%ramp_start_time = Time ! Record the current time
49490 OBC%ramping_is_activated = .true.
49500 OBC%trunc_ramp_time = OBC%ramp_timescale ! times 3.0 for tanh
4951 endif
4952 endif
49530 if (.not.OBC%ramping_is_activated) return
49540 deltaTime = max(0., time_minus_signed(Time, OBC%ramp_start_time, scale=US%s_to_T))
49550 if (deltaTime >= OBC%trunc_ramp_time) then
49560 OBC%ramp_value = 1.0
49570 OBC%ramp = .false. ! This turns off ramping after this call
4958 else
49590 wghtA = min( 1., deltaTime / OBC%ramp_timescale ) ! Linear profile in time
4960 !wghtA = wghtA*wghtA ! Convert linear profile to parabolic profile in time
4961 !wghtA = wghtA*wghtA*(3. - 2.*wghtA) ! Convert linear profile to cosine profile
4962 !wghtA = 1. - ( (1. - wghtA)**2 ) ! Convert linear profile to inverted parabolic profile
4963 !wghtA = tanh(wghtA) ! Convert linear profile to tanh
49640 OBC%ramp_value = wghtA
4965 endif
49660 write(msg(1:12),'(es12.3)') OBC%ramp_value
49670 call MOM_error(NOTE, "MOM_open_boundary: update_OBC_ramp set OBC ramp to "//trim(msg))
4968end subroutine update_OBC_ramp
4969
4970!> register open boundary objects for boundary updates.
49710subroutine register_OBC(name, param_file, Reg)
4972 character(len=32), intent(in) :: name !< OBC name used for error messages
4973 type(param_file_type), intent(in) :: param_file !< file to parse for model parameter values
4974 type(OBC_registry_type), pointer :: Reg !< pointer to the tracer registry
4975 integer :: nobc
4976 character(len=256) :: mesg ! Message for error messages.
4977
49780 if (.not. associated(Reg)) call OBC_registry_init(param_file, Reg)
4979
49800 if (Reg%nobc>=MAX_FIELDS_) then
4981 write(mesg, '("Increase MAX_FIELDS_ in MOM_memory.h to at least ",I0," to allow for &
49820 &all the open boundaries being registered via register_OBC.")') Reg%nobc+1
49830 call MOM_error(FATAL,"MOM register_OBC: "//mesg)
4984 endif
49850 Reg%nobc = Reg%nobc + 1
49860 nobc = Reg%nobc
4987
49880 Reg%OB(nobc)%name = name
4989
49900 if (Reg%locked) call MOM_error(FATAL, &
4991 "MOM register_OBC was called for OBC "//trim(Reg%OB(nobc)%name)//&
49920 " with a locked OBC registry.")
4993
49940end subroutine register_OBC
4995
4996!> This routine include declares and sets the variable "version".
49970subroutine OBC_registry_init(param_file, Reg)
4998 type(param_file_type), intent(in) :: param_file !< open file to parse for model parameters
4999 type(OBC_registry_type), pointer :: Reg !< pointer to OBC registry
5000
5001 integer, save :: init_calls = 0
5002
5003# include "version_variable.h"
5004 character(len=256) :: mesg ! Message for error messages.
5005
50060 if (.not.associated(Reg)) then ; allocate(Reg)
50070 else ; return ; endif
5008
5009 ! Read all relevant parameters and write them to the model log.
5010! call log_version(param_file, mdl, version, "")
5011
50120 init_calls = init_calls + 1
50130 if (init_calls > 1) then
50140 write(mesg,'("OBC_registry_init called ",I0," times with different registry pointers.")') init_calls
50150 if (is_root_pe()) call MOM_error(WARNING,"MOM_open_boundary: "//trim(mesg))
5016 endif
5017
5018end subroutine OBC_registry_init
5019
5020!> Add file to OBC registry.
50210function register_file_OBC(param_file, CS, US, OBC_Reg)
5022 type(param_file_type), intent(in) :: param_file !< parameter file.
5023 type(file_OBC_CS), pointer :: CS !< file control structure.
5024 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
5025 type(OBC_registry_type), pointer :: OBC_Reg !< OBC registry.
5026 logical :: register_file_OBC
5027 character(len=32) :: casename = "OBC file" !< This case's name.
5028
50290 if (associated(CS)) then
5030 call MOM_error(WARNING, "register_file_OBC called with an "// &
50310 "associated control structure.")
50320 return
5033 endif
50340 allocate(CS)
5035
5036 ! Register the file for boundary updates.
50370 call register_OBC(casename, param_file, OBC_Reg)
50380 register_file_OBC = .true.
5039
50400end function register_file_OBC
5041
5042!> Clean up the file OBC from registry.
50430subroutine file_OBC_end(CS)
5044 type(file_OBC_CS), pointer :: CS !< OBC file control structure.
5045
50460 if (associated(CS)) then
50470 deallocate(CS)
5048 endif
50490end subroutine file_OBC_end
5050
5051!> Initialize the segment tracer registry.
50520subroutine segment_tracer_registry_init(param_file, segment)
5053 type(param_file_type), intent(in) :: param_file !< open file to parse for model parameters
5054 type(OBC_segment_type), intent(inout) :: segment !< the segment
5055
5056 integer, save :: init_calls = 0
5057
5058! This include declares and sets the variable "version".
5059# include "version_variable.h"
5060 character(len=40) :: mdl = "segment_tracer_registry_init" ! This routine's name.
5061 !character(len=256) :: mesg ! Message for error messages.
5062
50630 if (.not.associated(segment%tr_Reg)) then
50640 allocate(segment%tr_Reg)
5065 else
50660 return
5067 endif
5068
50690 init_calls = init_calls + 1
5070
5071 ! Read all relevant parameters and write them to the model log.
50720 if (init_calls == 1) call log_version(param_file, mdl, version, "")
5073
5074end subroutine segment_tracer_registry_init
5075
5076!> Initialize all the segment thickness reservoirs.
50770subroutine segment_thickness_reservoir_init(GV, US, OBC, param_file)
5078 type(param_file_type), intent(in) :: param_file !< open file to parse for model parameters
5079 type(verticalGrid_type), intent(in) :: GV !< ocean vertical grid structure
5080 type(unit_scale_type), intent(in) :: US !< Unit scaling type
5081 type(ocean_OBC_type), pointer :: OBC !< Open boundary structure
5082! real, optional, intent(in) :: OBC_scalar !< If present, use scalar value for segment tracer
5083! !! inflow concentration, including any rescaling to
5084! !! put the tracer concentration into its internal units,
5085! !! like [S ~> ppt] for salinity.
5086! logical, optional, intent(in) :: OBC_array !< If true, use array values for segment tracer
5087! !! inflow concentration.
5088! Local variables
5089 real :: rescale ! A multiplicatively corrected scaling factor, in units like [S ppt-1 ~> 1] for
5090 ! salinity, or other various units depending on what rescaling has occurred previously.
5091 integer :: nseg, m, isd, ied, jsd, jed, IsdB, IedB, JsdB, JedB
5092 integer :: fd_id
5093 type(OBC_segment_type), pointer :: segment => NULL() ! pointer to segment type list
5094 integer, save :: init_calls = 0
5095
5096! This include declares and sets the variable "version".
5097# include "version_variable.h"
5098 character(len=40) :: mdl = "segment_thickness_reservoir_init" ! This routine's name.
5099
51000 if (.not. associated(OBC)) return
5101
51020 do nseg=1, OBC%number_of_segments
51030 segment=>OBC%segment(nseg)
51040 if (.not. segment%on_pe) cycle
5105
51060 if (associated(segment%h_Reg)) &
51070 call MOM_error(FATAL,"segment_thickness_reservoir_init: thickness array was previously allocated")
51080 allocate(segment%h_Reg)
5109
51100 isd = segment%HI%isd ; ied = segment%HI%ied
51110 jsd = segment%HI%jsd ; jed = segment%HI%jed
51120 IsdB = segment%HI%IsdB ; IedB = segment%HI%IedB
51130 JsdB = segment%HI%JsdB ; JedB = segment%HI%JedB
5114
51150 fd_id = -1
51160 do m=1,segment%num_fields
51170 if (lowercase(segment%field(m)%name) == lowercase(segment%h_Reg%name)) fd_id = m
5118 enddo
51190 segment%h_Reg%scale = US%Z_to_m
51200 do m=1,segment%num_fields
51210 if (uppercase(segment%field(m)%name) == uppercase(segment%h_Reg%name)) then
51220 if (.not. segment%field(m)%use_IO) then
51230 rescale = 1.0
51240 if ((segment%field(m)%scale /= 0.0) .and. (segment%field(m)%scale /= 1.0)) &
51250 rescale = 1.0 / segment%field(m)%scale
51260 segment%field(m)%value = rescale * segment%field(m)%value
5127 endif
5128 endif
5129 enddo
5130
51310 if (segment%is_E_or_W) then
51320 allocate(segment%h_Reg%h(IsdB:IedB,jsd:jed,1:GV%ke), source=0.0)
51330 allocate(segment%h_Reg%h_res(IsdB:IedB,jsd:jed,1:GV%ke), source=0.0)
51340 elseif (segment%is_N_or_S) then
51350 allocate(segment%h_Reg%h(isd:ied,JsdB:JedB,1:GV%ke), source=0.0)
51360 allocate(segment%h_Reg%h_res(isd:ied,JsdB:JedB,1:GV%ke), source=0.0)
5137 endif
51380 segment%h_Reg%is_initialized = .false.
5139
51400 init_calls = init_calls + 1
5141
5142 ! Read all relevant parameters and write them to the model log.
51430 if (init_calls == 1) call log_version(param_file, mdl, version, "")
5144 enddo
5145
5146end subroutine segment_thickness_reservoir_init
5147
5148!> Register a tracer array that is active on an OBC segment, potentially also specifying how the
5149!! tracer inflow values are specified.
51500subroutine register_segment_tracer(tr_ptr, ntr_index, param_file, GV, segment, &
5151 OBC_scalar, OBC_array, scale, fd_index)
5152 type(verticalGrid_type), intent(in) :: GV !< ocean vertical grid structure
5153 type(tracer_type), target :: tr_ptr !< A target that can be used to set a pointer to the
5154 !! stored value of tr. This target must be
5155 !! an enduring part of the control structure,
5156 !! because the tracer registry will use this memory,
5157 !! but it also means that any updates to this
5158 !! structure in the calling module will be
5159 !! available subsequently to the tracer registry.
5160 integer, intent(in) :: ntr_index !< index of segment tracer in the global tracer registry
5161 type(param_file_type), intent(in) :: param_file !< file to parse for model parameter values
5162 type(OBC_segment_type), intent(inout) :: segment !< current segment data structure
5163 real, optional, intent(in) :: OBC_scalar !< If present, use scalar value for segment tracer
5164 !! inflow concentration, including any rescaling to
5165 !! put the tracer concentration into its internal units,
5166 !! like [S ~> ppt] for salinity.
5167 logical, optional, intent(in) :: OBC_array !< If true, use array values for segment tracer
5168 !! inflow concentration.
5169 real, optional, intent(in) :: scale !< A scaling factor that should be used with any
5170 !! data that is read in to convert it to the internal
5171 !! units of this tracer, in units like [S ppt-1 ~> 1]
5172 !! for salinity.
5173 integer, optional, intent(in) :: fd_index !< index of segment tracer in the input field
5174
5175! Local variables
5176 real :: rescale ! A multiplicatively corrected scaling factor, in units like [S ppt-1 ~> 1] for
5177 ! salinity, or other various units depending on what rescaling has occurred previously.
5178 integer :: ntseg, m, isd, ied, jsd, jed, IsdB, IedB, JsdB, JedB
5179 character(len=256) :: mesg ! Message for error messages.
5180
51810 call segment_tracer_registry_init(param_file, segment)
5182
51830 if (segment%tr_Reg%ntseg>=MAX_FIELDS_) then
5184 write(mesg,'("Increase MAX_FIELDS_ in MOM_memory.h to at least ",I0," to allow for &
51850 &all the tracers being registered via register_segment_tracer.")') segment%tr_Reg%ntseg+1
51860 call MOM_error(FATAL,"MOM register_segment_tracer: "//mesg)
5187 endif
51880 segment%tr_Reg%ntseg = segment%tr_Reg%ntseg + 1
51890 ntseg = segment%tr_Reg%ntseg
5190
51910 isd = segment%HI%isd ; ied = segment%HI%ied
51920 jsd = segment%HI%jsd ; jed = segment%HI%jed
51930 IsdB = segment%HI%IsdB ; IedB = segment%HI%IedB
51940 JsdB = segment%HI%JsdB ; JedB = segment%HI%JedB
5195
51960 segment%tr_Reg%Tr(ntseg)%Tr => tr_ptr
51970 segment%tr_Reg%Tr(ntseg)%name = tr_ptr%name
51980 segment%tr_Reg%Tr(ntseg)%ntr_index = ntr_index
51990 if (present(fd_index)) segment%tr_Reg%Tr(ntseg)%fd_index = fd_index
5200
52010 segment%tr_Reg%Tr(ntseg)%scale = 1.0
52020 if (present(scale)) then
52030 segment%tr_Reg%Tr(ntseg)%scale = scale
52040 do m=1,segment%num_fields
5205 ! Store the scaling factor for fields with exactly matching names, and possibly
5206 ! rescale the previously stored input values. Note that calls to register_segment_tracer
5207 ! can come before or after calls to initialize_segment_data.
52080 if (uppercase(segment%field(m)%name) == uppercase(segment%tr_Reg%Tr(ntseg)%name)) then
52090 if (.not. segment%field(m)%use_IO) then
52100 rescale = scale
52110 if ((segment%field(m)%scale /= 0.0) .and. (segment%field(m)%scale /= 1.0)) &
52120 rescale = scale / segment%field(m)%scale
52130 segment%field(m)%value = rescale * segment%field(m)%value
5214 endif
52150 segment%field(m)%scale = scale
5216 endif
5217 enddo
5218 endif
5219
52200 if (segment%tr_Reg%locked) call MOM_error(FATAL, &
5221 "MOM register_segment_tracer was called for variable "//trim(segment%tr_Reg%Tr(ntseg)%name)//&
52220 " with a locked tracer registry.")
5223
52240 if (present(OBC_scalar)) segment%tr_Reg%Tr(ntseg)%OBC_inflow_conc = OBC_scalar ! initialize tracer value later
52250 if (present(OBC_array)) then
52260 if (segment%is_E_or_W) then
52270 allocate(segment%tr_Reg%Tr(ntseg)%t(IsdB:IedB,jsd:jed,1:GV%ke), source=0.0)
52280 allocate(segment%tr_Reg%Tr(ntseg)%tres(IsdB:IedB,jsd:jed,1:GV%ke), source=0.0)
52290 segment%tr_Reg%Tr(ntseg)%is_initialized = .false.
52300 elseif (segment%is_N_or_S) then
52310 allocate(segment%tr_Reg%Tr(ntseg)%t(isd:ied,JsdB:JedB,1:GV%ke), source=0.0)
52320 allocate(segment%tr_Reg%Tr(ntseg)%tres(isd:ied,JsdB:JedB,1:GV%ke), source=0.0)
52330 segment%tr_Reg%Tr(ntseg)%is_initialized = .false.
5234 endif
5235 endif
5236
52370end subroutine register_segment_tracer
5238
5239!> Clean up the segment tracer registry.
52400subroutine segment_tracer_registry_end(Reg)
5241 type(segment_tracer_registry_type), pointer :: Reg !< pointer to tracer registry
5242
5243! Local variables
5244 integer n
5245
52460 if (associated(Reg)) then
52470 do n = 1, Reg%ntseg
52480 if (allocated(Reg%Tr(n)%t)) deallocate(Reg%Tr(n)%t)
5249 enddo
52500 deallocate(Reg)
5251 endif
52520end subroutine segment_tracer_registry_end
5253
5254!> Clean up the segment thickness object
52550subroutine segment_thickness_registry_end(Reg)
5256 type(OBC_segment_thickness_type), pointer :: Reg !< pointer to thickness reservoir
5257
5258! Local variables
5259
52600 if (associated(Reg)) then
52610 if (allocated(Reg%h)) deallocate(Reg%h)
52620 if (allocated(Reg%h_res)) deallocate(Reg%h_res)
52630 deallocate(Reg)
5264 endif
52650end subroutine segment_thickness_registry_end
5266
5267!> Registers the temperature and salinity in the segment tracer registry.
52680subroutine register_temp_salt_segments(GV, US, OBC, tr_Reg, param_file)
5269 type(verticalGrid_type), intent(in) :: GV !< ocean vertical grid structure
5270 type(unit_scale_type), intent(in) :: US !< Unit scaling type
5271 type(ocean_OBC_type), pointer :: OBC !< Open boundary structure
5272 type(tracer_registry_type), pointer :: tr_Reg !< Tracer registry
5273 type(param_file_type), intent(in) :: param_file !< file to parse for model parameter values
5274
5275 ! Local variables
5276 integer :: n, ntr_id
5277 character(len=32) :: name
5278 type(OBC_segment_type), pointer :: segment => NULL() ! pointer to segment type list
5279 type(tracer_type), pointer :: tr_ptr => NULL()
5280
52810 if (.not. associated(OBC)) return
5282
52830 do n=1,OBC%number_of_segments
52840 segment => OBC%segment(n)
52850 if (.not. segment%on_pe) cycle
5286
52870 if (associated(segment%tr_Reg)) &
52880 call MOM_error(FATAL,"register_temp_salt_segments: tracer array was previously allocated")
5289
52900 name = 'temp'
52910 call tracer_name_lookup(tr_Reg, ntr_id, tr_ptr, name)
5292 call register_segment_tracer(tr_ptr, ntr_id, param_file, GV, segment, &
52930 OBC_array=segment%temp_segment_data_exists, scale=US%degC_to_C)
52940 name = 'salt'
52950 call tracer_name_lookup(tr_Reg, ntr_id, tr_ptr, name)
5296 call register_segment_tracer(tr_ptr, ntr_id, param_file, GV, segment, &
52970 OBC_array=segment%salt_segment_data_exists, scale=US%ppt_to_S)
5298 enddo
5299
5300end subroutine register_temp_salt_segments
5301
5302!> Sets the OBC properties of external obgc tracers, such as their source file and field name
53030subroutine set_obgc_segments_props(OBC,tr_name,obc_src_file_name,obc_src_field_name,lfac_in,lfac_out)
5304 type(ocean_OBC_type),pointer :: OBC !< Open boundary structure
5305 character(len=*), intent(in) :: tr_name !< Tracer name
5306 character(len=*), intent(in) :: obc_src_file_name !< OBC source file name
5307 character(len=*), intent(in) :: obc_src_field_name !< name of the field in the source file
5308 real, intent(in) :: lfac_in !< factors for tracer reservoir inbound length scales [nondim]
5309 real, intent(in) :: lfac_out !< factors for tracer reservoir outbound length scales [nondim]
5310
5311 type(external_tracers_segments_props),pointer :: node_ptr => NULL() !pointer to type that keeps
5312 ! the tracer segment properties
53130 allocate(node_ptr)
53140 node_ptr%tracer_name = trim(tr_name)
53150 node_ptr%tracer_src_file = trim(obc_src_file_name)
53160 node_ptr%tracer_src_field = trim(obc_src_field_name)
53170 node_ptr%lfac_in = lfac_in
53180 node_ptr%lfac_out = lfac_out
5319 ! Reversed Linked List implementation! Make this new node to be the head of the list.
53200 node_ptr%next => OBC%obgc_segments_props
53210 OBC%obgc_segments_props => node_ptr
53220 OBC%num_obgc_tracers = OBC%num_obgc_tracers+1
53230end subroutine set_obgc_segments_props
5324
5325!> Get the OBC properties of external obgc tracers, such as their source file, field name,
5326!! reservoir length scale factors
53270subroutine get_obgc_segments_props(node, tr_name,obc_src_file_name,obc_src_field_name,lfac_in,lfac_out)
5328 type(external_tracers_segments_props),pointer :: node !< pointer to tracer segment properties
5329 character(len=*), intent(out) :: tr_name !< Tracer name
5330 character(len=*), intent(out) :: obc_src_file_name !< OBC source file name
5331 character(len=*), intent(out) :: obc_src_field_name !< name of the field in the source file
5332 real, intent(out) :: lfac_in !< multiplicative factor for inbound reservoir length scale [nondim]
5333 real, intent(out) :: lfac_out !< multiplicative factor for outbound reservoir length scale [nondim]
53340 tr_name = trim(node%tracer_name)
53350 obc_src_file_name = trim(node%tracer_src_file)
53360 obc_src_field_name = trim(node%tracer_src_field)
53370 lfac_in = node%lfac_in
53380 lfac_out = node%lfac_out
53390 node => node%next
53400end subroutine get_obgc_segments_props
5341
5342!> Registers a named tracer in the segment tracer registries for the OBC segments on which it is active.
53430subroutine register_obgc_segments(GV, OBC, tr_Reg, param_file, tr_name)
5344 type(verticalGrid_type), intent(in) :: GV !< ocean vertical grid structure
5345 type(ocean_OBC_type), pointer :: OBC !< Open boundary structure
5346 type(tracer_registry_type), pointer :: tr_Reg !< Tracer registry
5347 type(param_file_type), intent(in) :: param_file !< file to parse for model parameter values
5348 character(len=*), intent(in) :: tr_name !< Tracer name
5349! Local variables
5350 integer :: ntr_id, fd_id
5351 integer :: n, m
5352 type(OBC_segment_type), pointer :: segment => NULL() ! pointer to segment type list
5353 type(tracer_type), pointer :: tr_ptr => NULL()
5354
53550 if (.not. associated(OBC)) return
5356
53570 do n=1,OBC%number_of_segments
53580 segment => OBC%segment(n)
53590 if (.not. segment%on_pe) cycle
53600 call tracer_name_lookup(tr_Reg, ntr_id, tr_ptr, tr_name)
5361 ! get the obgc field index
53620 fd_id = -1
53630 do m=1,segment%num_fields
53640 if (lowercase(segment%field(m)%name) == lowercase(tr_name)) fd_id = m
5365 enddo
53660 call register_segment_tracer(tr_ptr, ntr_id, param_file, GV, segment, OBC_array=.True., fd_index=fd_id)
5367 enddo
5368
53690end subroutine register_obgc_segments
5370
5371!> Stores the interior tracer values on the segment, and in some cases also sets the tracer reservoir values.
53720subroutine fill_obgc_segments(G, GV, OBC, tr_ptr, tr_name)
5373 type(ocean_grid_type), intent(inout) :: G !< Ocean grid structure
5374 type(verticalGrid_type), intent(in) :: GV !< ocean vertical grid structure
5375 type(ocean_OBC_type), pointer :: OBC !< Open boundary structure
5376 real, dimension(:,:,:), pointer :: tr_ptr !< Pointer to tracer field in scaled concentration
5377 !! units, like [S ~> ppt] for salinity.
5378 character(len=*), intent(in) :: tr_name !< Tracer name
5379! Local variables
5380 integer :: isd, ied, IsdB, IedB, jsd, jed, JsdB, JedB, n, nz, nt
5381 integer :: i, j, k
5382 type(OBC_segment_type), pointer :: segment => NULL() ! pointer to segment type list
5383 real :: I_scale ! A factor that unscales the internal units of a tracer, like [ppt S-1 ~> 1] for salinity
5384
53850 if (.not. associated(OBC)) return
53860 call pass_var(tr_ptr, G%Domain)
53870 nz = G%ke
53880 do n=1,OBC%number_of_segments
53890 segment => OBC%segment(n)
53900 if (.not. segment%on_pe) cycle
53910 nt = get_tracer_index(segment, tr_name)
53920 if (nt < 0) then
53930 call MOM_error(FATAL,"fill_obgc_segments: Did not find tracer "// tr_name)
5394 endif
53950 isd = segment%HI%isd ; ied = segment%HI%ied
53960 jsd = segment%HI%jsd ; jed = segment%HI%jed
53970 IsdB = segment%HI%IsdB ; IedB = segment%HI%IedB
53980 JsdB = segment%HI%JsdB ; JedB = segment%HI%JedB
5399
5400 ! Fill segments with Tracer values
54010 if (segment%direction == OBC_DIRECTION_W) then
54020 I = segment%HI%IsdB
54030 do k=1,nz ; do j=segment%HI%jsd,segment%HI%jed
54040 segment%tr_Reg%Tr(nt)%t(I,j,k) = tr_ptr(i+1,j,k)
5405 enddo ; enddo
54060 elseif (segment%direction == OBC_DIRECTION_E) then
54070 I = segment%HI%IsdB
54080 do k=1,nz ; do j=segment%HI%jsd,segment%HI%jed
54090 segment%tr_Reg%Tr(nt)%t(I,j,k) = tr_ptr(i,j,k)
5410 enddo ; enddo
54110 elseif (segment%direction == OBC_DIRECTION_S) then
54120 J = segment%HI%JsdB
54130 do k=1,nz ; do i=segment%HI%isd,segment%HI%ied
54140 segment%tr_Reg%Tr(nt)%t(i,J,k) = tr_ptr(i,j+1,k)
5415 enddo ; enddo
54160 elseif (segment%direction == OBC_DIRECTION_N) then
54170 J = segment%HI%JsdB
54180 do k=1,nz ; do i=segment%HI%isd,segment%HI%ied
54190 segment%tr_Reg%Tr(nt)%t(i,J,k) = tr_ptr(i,j,k)
5420 enddo ; enddo
5421 endif
5422
54230 if (.not.segment%tr_Reg%Tr(nt)%is_initialized) &
54240 segment%tr_Reg%Tr(nt)%tres(:,:,:) = segment%tr_Reg%Tr(nt)%t(:,:,:)
5425
54260 if (OBC%reservoir_init_bug) then
5427 ! OBC%tres_x and OBC%tres_y should not be set here, but in a subsequent call to setup_OBC_tracer_reservoirs.
5428 ! Note that fill_obgc_segments is not called for runs that start from a restart file.
54290 I_scale = 1.0
54300 if (segment%tr_Reg%Tr(nt)%scale /= 0.0) I_scale = 1.0 / segment%tr_Reg%Tr(nt)%scale
54310 if (segment%is_E_or_W) then
54320 if (allocated(OBC%tres_x)) then
54330 I = segment%HI%IsdB
54340 do k=1,nz ; do j=segment%HI%jsd,segment%HI%jed
54350 OBC%tres_x(I,j,k,nt) = I_scale * segment%tr_Reg%Tr(nt)%tres(I,j,k)
5436 enddo ; enddo
5437 endif
5438 else ! segment%is_N_or_S
54390 if (allocated(OBC%tres_y)) then
54400 J = segment%HI%JsdB
54410 do k=1,nz ; do i=segment%HI%isd,segment%HI%ied
54420 OBC%tres_y(i,J,k,nt) = I_scale * segment%tr_Reg%Tr(nt)%tres(i,J,k)
5443 enddo ; enddo
5444 endif
5445 endif
5446 endif
5447
5448 enddo ! End of loop over segments.
5449
54500end subroutine fill_obgc_segments
5451
5452!> Set the value of temperatures and salinities on OBC segments
54530subroutine fill_temp_salt_segments(G, GV, US, OBC, tv)
5454 type(ocean_grid_type), intent(in) :: G !< Ocean grid structure
5455 type(verticalGrid_type), intent(in) :: GV !< ocean vertical grid structure
5456 type(unit_scale_type), intent(in) :: US !< Unit scaling
5457 type(ocean_OBC_type), pointer :: OBC !< Open boundary structure
5458 type(thermo_var_ptrs), intent(in) :: tv !< Thermodynamics structure
5459
5460 integer :: isd, ied, IsdB, IedB, jsd, jed, JsdB, JedB, n, nz
5461 integer :: i, j, k
5462 type(OBC_segment_type), pointer :: segment => NULL() ! pointer to segment type list
5463
54640 if (.not. associated(OBC)) return
54650 if (.not. associated(tv%T) .and. associated(tv%S)) return
5466 ! Both temperature and salinity fields
5467
54680 nz = GV%ke
5469
54700 do n=1,OBC%number_of_segments
54710 segment => OBC%segment(n)
54720 if (.not. segment%on_pe) cycle
5473
54740 isd = segment%HI%isd ; ied = segment%HI%ied
54750 jsd = segment%HI%jsd ; jed = segment%HI%jed
54760 IsdB = segment%HI%IsdB ; IedB = segment%HI%IedB
54770 JsdB = segment%HI%JsdB ; JedB = segment%HI%JedB
5478
5479 ! Fill with T and S values
54800 if (segment%is_E_or_W) then
54810 I=segment%HI%IsdB
54820 do k=1,nz ; do j=segment%HI%jsd,segment%HI%jed
54830 if (segment%direction == OBC_DIRECTION_W) then
54840 segment%tr_Reg%Tr(1)%t(I,j,k) = tv%T(i+1,j,k)
54850 segment%tr_Reg%Tr(2)%t(I,j,k) = tv%S(i+1,j,k)
5486 else
54870 segment%tr_Reg%Tr(1)%t(I,j,k) = tv%T(i,j,k)
54880 segment%tr_Reg%Tr(2)%t(I,j,k) = tv%S(i,j,k)
5489 endif
5490 enddo ; enddo
5491 else
54920 J=segment%HI%JsdB
54930 do k=1,nz ; do i=segment%HI%isd,segment%HI%ied
54940 if (segment%direction == OBC_DIRECTION_S) then
54950 segment%tr_Reg%Tr(1)%t(i,J,k) = tv%T(i,j+1,k)
54960 segment%tr_Reg%Tr(2)%t(i,J,k) = tv%S(i,j+1,k)
5497 else
54980 segment%tr_Reg%Tr(1)%t(i,J,k) = tv%T(i,j,k)
54990 segment%tr_Reg%Tr(2)%t(i,J,k) = tv%S(i,j,k)
5500 endif
5501 enddo ; enddo
5502 endif
55030 if (.not.segment%tr_Reg%Tr(1)%is_initialized) &
55040 segment%tr_Reg%Tr(1)%tres(:,:,:) = segment%tr_Reg%Tr(1)%t(:,:,:)
55050 if (.not.segment%tr_Reg%Tr(2)%is_initialized) &
55060 segment%tr_Reg%Tr(2)%tres(:,:,:) = segment%tr_Reg%Tr(2)%t(:,:,:)
5507 enddo
5508
5509end subroutine fill_temp_salt_segments
5510
5511!> Set the value of temperatures and salinities on OBC segments
55120subroutine fill_thickness_segments(G, GV, US, OBC, h)
5513 type(ocean_grid_type), intent(in) :: G !< Ocean grid structure
5514 type(verticalGrid_type), intent(in) :: GV !< ocean vertical grid structure
5515 type(unit_scale_type), intent(in) :: US !< Unit scaling
5516 type(ocean_OBC_type), pointer :: OBC !< Open boundary structure
5517 real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(in) :: h !< Layer thicknesses [H ~> m or kg m-2]
5518
5519 integer :: isd, ied, IsdB, IedB, jsd, jed, JsdB, JedB, n, nz
5520 integer :: i, j, k
5521 type(OBC_segment_type), pointer :: segment => NULL() ! pointer to segment type list
5522
55230 if (.not. associated(OBC)) return
5524 ! Both temperature and salinity fields
5525
55260 nz = GV%ke
5527
55280 do n=1, OBC%number_of_segments
55290 segment => OBC%segment(n)
55300 if (.not. segment%on_pe) cycle
5531
55320 isd = segment%HI%isd ; ied = segment%HI%ied
55330 jsd = segment%HI%jsd ; jed = segment%HI%jed
55340 IsdB = segment%HI%IsdB ; IedB = segment%HI%IedB
55350 JsdB = segment%HI%JsdB ; JedB = segment%HI%JedB
5536
5537 ! Fill with thickness
55380 if (segment%is_E_or_W) then
55390 I=segment%HI%IsdB
55400 do k=1,nz ; do j=segment%HI%jsd,segment%HI%jed
55410 if (segment%direction == OBC_DIRECTION_W) then
55420 segment%h_Reg%h(I,j,k) = h(i+1,j,k)
5543 else
55440 segment%h_Reg%h(I,j,k) = h(i,j,k)
5545 endif
5546 enddo ; enddo
5547 else
55480 J=segment%HI%JsdB
55490 do k=1,nz ; do i=segment%HI%isd,segment%HI%ied
55500 if (segment%direction == OBC_DIRECTION_S) then
55510 segment%h_Reg%h(i,J,k) = h(i,j+1,k)
5552 else
55530 segment%h_Reg%h(i,J,k) = h(i,j,k)
5554 endif
5555 enddo ; enddo
5556 endif
55570 if (.not.segment%h_Reg%is_initialized) then
55580 segment%h_Reg%h_res(:,:,:) = segment%h_Reg%h(:,:,:)
55590 segment%h_Reg%is_initialized = .true.
5560 endif
5561 enddo
5562
5563end subroutine fill_thickness_segments
5564
5565!> Find the region outside of all open boundary segments and
5566!! make sure it is set to land mask. Gonna need to know global land
5567!! mask as well to get it right...
55680subroutine mask_outside_OBCs(G, US, param_file, OBC)
5569 type(dyn_horgrid_type), intent(inout) :: G !< Ocean grid structure
5570 type(param_file_type), intent(in) :: param_file !< Parameter file handle
5571 type(ocean_OBC_type), pointer :: OBC !< Open boundary structure
5572 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
5573
5574 ! Local variables
5575 integer :: i, j
5576 logical :: fatal_error = .False.
5577 real :: min_depth ! The minimum depth for ocean points [Z ~> m]
5578 real :: mask_depth ! The masking depth for ocean points [Z ~> m]
5579 real :: Dmask ! The depth for masking in the same units as G%bathyT [Z ~> m].
5580 integer, parameter :: cin = 3, cout = 4, cland = -1, cedge = -2
5581 character(len=256) :: mesg ! Message for error messages.
55820 real, allocatable, dimension(:,:) :: color, color2 ! For sorting inside from outside,
5583 ! two different ways [nondim]
5584
55850 if (.not. associated(OBC)) return
5586
5587 call get_param(param_file, mdl, "MINIMUM_DEPTH", min_depth, &
55880 units="m", default=0.0, scale=US%m_to_Z, do_not_log=.true.)
5589 call get_param(param_file, mdl, "MASKING_DEPTH", mask_depth, &
55900 units="m", default=-9999.0, scale=US%m_to_Z, do_not_log=.true.)
5591
55920 Dmask = mask_depth
55930 if (mask_depth == -9999.0*US%m_to_Z) Dmask = min_depth
5594
5595 ! The reference depth on a dyn_horgrid is 0, otherwise would need: min_depth = min_depth - G%Z_ref
5596
55970 allocate(color(G%isd:G%ied, G%jsd:G%jed), source=0.0)
55980 allocate(color2(G%isd:G%ied, G%jsd:G%jed), source=0.0)
5599
5600 ! Paint a frame around the outside.
56010 do j=G%jsd,G%jed
56020 color(G%isd,j) = cedge
56030 color(G%ied,j) = cedge
56040 color2(G%isd,j) = cedge
56050 color2(G%ied,j) = cedge
5606 enddo
56070 do i=G%isd,G%ied
56080 color(i,G%jsd) = cedge
56090 color(i,G%jed) = cedge
56100 color2(i,G%jsd) = cedge
56110 color2(i,G%jed) = cedge
5612 enddo
5613
5614 ! Set color to cland in the land. Note that this is before the land
5615 ! mask has been initialized, set mask values based on depth.
56160 do j=G%jsd,G%jed
56170 do i=G%isd,G%ied
56180 if (G%bathyT(i,j) <= min_depth) color(i,j) = cland
56190 if (G%bathyT(i,j) <= min_depth) color2(i,j) = cland
5620 enddo
5621 enddo
5622
56230 do j=G%jsd,G%jed ; do i=G%IsdB+1,G%IedB-1
56240 if (OBC%segnum_u(I,j) < 0) then ! OBC_DIRECTION_W
56250 if (color(i,j) == 0.0) color(i,j) = cout
56260 if (color(i+1,j) == 0.0) color(i+1,j) = cin
56270 elseif (OBC%segnum_u(I,j) > 0) then ! OBC_DIRECTION_E
56280 if (color(i,j) == 0.0) color(i,j) = cin
56290 if (color(i+1,j) == 0.0) color(i+1,j) = cout
5630 endif
5631 enddo ; enddo
56320 do J=G%JsdB+1,G%JedB-1 ; do i=G%isd,G%ied
56330 if (OBC%segnum_v(i,J) < 0) then ! OBC_DIRECTION_S
56340 if (color(i,j) == 0.0) color(i,j) = cout
56350 if (color(i,j+1) == 0.0) color(i,j+1) = cin
56360 elseif (OBC%segnum_v(i,J) > 0) then ! OBC_DIRECTION_N
56370 if (color(i,j) == 0.0) color(i,j) = cin
56380 if (color(i,j+1) == 0.0) color(i,j+1) = cout
5639 endif
5640 enddo ; enddo
5641
56420 do J=G%JsdB+1,G%JedB-1 ; do i=G%isd,G%ied
56430 if (OBC%segnum_v(i,J) < 0) then ! OBC_DIRECTION_S
56440 if (color2(i,j) == 0.0) color2(i,j) = cout
56450 if (color2(i,j+1) == 0.0) color2(i,j+1) = cin
56460 elseif (OBC%segnum_v(i,J) > 0) then ! OBC_DIRECTION_N
56470 if (color2(i,j) == 0.0) color2(i,j) = cin
56480 if (color2(i,j+1) == 0.0) color2(i,j+1) = cout
5649 endif
5650 enddo ; enddo
56510 do j=G%jsd,G%jed ; do i=G%IsdB+1,G%IedB-1
56520 if (OBC%segnum_u(I,j) < 0) then ! OBC_DIRECTION_W
56530 if (color2(i,j) == 0.0) color2(i,j) = cout
56540 if (color2(i+1,j) == 0.0) color2(i+1,j) = cin
56550 elseif (OBC%segnum_u(I,j) > 0) then ! OBC_DIRECTION_E
56560 if (color2(i,j) == 0.0) color2(i,j) = cin
56570 if (color2(i+1,j) == 0.0) color2(i+1,j) = cout
5658 endif
5659 enddo ; enddo
5660
5661 ! Do the flood fill until there are no more uncolored cells.
56620 call flood_fill(G, color, cin, cout, cland)
56630 call flood_fill2(G, color2, cin, cout, cland)
5664
5665 ! Use the color to set outside to min_depth on this process.
56660 do j=G%jsd,G%jed ; do i=G%isd,G%ied
56670 if (color(i,j) /= color2(i,j)) then
56680 fatal_error = .True.
5669 write(mesg,'("MOM_open_boundary: problem with OBC segments specification at ",I0,",",I0," during\n", &
56700 &"the masking of the outside grid points.")') i, j
56710 call MOM_error(WARNING,"MOM mask_outside_OBCs: "//mesg, all_print=.true.)
5672 endif
56730 if (color(i,j) == cout) G%bathyT(i,j) = Dmask
5674 enddo ; enddo
56750 if (fatal_error) call MOM_error(FATAL, &
56760 "MOM_open_boundary: inconsistent OBC segments.")
5677
56780 deallocate(color)
56790 deallocate(color2)
56800end subroutine mask_outside_OBCs
5681
5682!> flood the cin, cout values
56830subroutine flood_fill(G, color, cin, cout, cland)
5684 type(dyn_horgrid_type), intent(inout) :: G !< Ocean grid structure
5685 real, dimension(:,:), intent(inout) :: color !< For sorting inside from outside [nondim]
5686 integer, intent(in) :: cin !< color for inside the domain
5687 integer, intent(in) :: cout !< color for outside the domain
5688 integer, intent(in) :: cland !< color for inside the land mask
5689
5690! Local variables
5691 integer :: i, j, ncount
5692
56930 ncount = 1
56940 do while (ncount > 0)
56950 ncount = 0
56960 do j=G%jsd+1,G%jed-1
56970 do i=G%isd+1,G%ied-1
56980 if (color(i,j) == 0.0 .and. color(i-1,j) > 0.0) then
56990 color(i,j) = color(i-1,j)
57000 ncount = ncount + 1
5701 endif
57020 if (color(i,j) == 0.0 .and. color(i+1,j) > 0.0) then
57030 color(i,j) = color(i+1,j)
57040 ncount = ncount + 1
5705 endif
57060 if (color(i,j) == 0.0 .and. color(i,j-1) > 0.0) then
57070 color(i,j) = color(i,j-1)
57080 ncount = ncount + 1
5709 endif
57100 if (color(i,j) == 0.0 .and. color(i,j+1) > 0.0) then
57110 color(i,j) = color(i,j+1)
57120 ncount = ncount + 1
5713 endif
5714 enddo
5715 enddo
57160 do j=G%jed-1,G%jsd+1,-1
57170 do i=G%ied-1,G%isd+1,-1
57180 if (color(i,j) == 0.0 .and. color(i-1,j) > 0.0) then
57190 color(i,j) = color(i-1,j)
57200 ncount = ncount + 1
5721 endif
57220 if (color(i,j) == 0.0 .and. color(i+1,j) > 0.0) then
57230 color(i,j) = color(i+1,j)
57240 ncount = ncount + 1
5725 endif
57260 if (color(i,j) == 0.0 .and. color(i,j-1) > 0.0) then
57270 color(i,j) = color(i,j-1)
57280 ncount = ncount + 1
5729 endif
57300 if (color(i,j) == 0.0 .and. color(i,j+1) > 0.0) then
57310 color(i,j) = color(i,j+1)
57320 ncount = ncount + 1
5733 endif
5734 enddo
5735 enddo
57360 call pass_var(color, G%Domain)
57370 call sum_across_PEs(ncount)
5738 enddo
5739
57400end subroutine flood_fill
5741
5742!> flood the cin, cout values
57430subroutine flood_fill2(G, color, cin, cout, cland)
5744 type(dyn_horgrid_type), intent(inout) :: G !< Ocean grid structure
5745 real, dimension(:,:), intent(inout) :: color !< For sorting inside from outside [nondim]
5746 integer, intent(in) :: cin !< color for inside the domain
5747 integer, intent(in) :: cout !< color for outside the domain
5748 integer, intent(in) :: cland !< color for inside the land mask
5749
5750! Local variables
5751 integer :: i, j, ncount
5752
57530 ncount = 1
57540 do while (ncount > 0)
57550 ncount = 0
57560 do i=G%isd+1,G%ied-1
57570 do j=G%jsd+1,G%jed-1
57580 if (color(i,j) == 0.0 .and. color(i-1,j) > 0.0) then
57590 color(i,j) = color(i-1,j)
57600 ncount = ncount + 1
5761 endif
57620 if (color(i,j) == 0.0 .and. color(i+1,j) > 0.0) then
57630 color(i,j) = color(i+1,j)
57640 ncount = ncount + 1
5765 endif
57660 if (color(i,j) == 0.0 .and. color(i,j-1) > 0.0) then
57670 color(i,j) = color(i,j-1)
57680 ncount = ncount + 1
5769 endif
57700 if (color(i,j) == 0.0 .and. color(i,j+1) > 0.0) then
57710 color(i,j) = color(i,j+1)
57720 ncount = ncount + 1
5773 endif
5774 enddo
5775 enddo
57760 do i=G%ied-1,G%isd+1,-1
57770 do j=G%jed-1,G%jsd+1,-1
57780 if (color(i,j) == 0.0 .and. color(i-1,j) > 0.0) then
57790 color(i,j) = color(i-1,j)
57800 ncount = ncount + 1
5781 endif
57820 if (color(i,j) == 0.0 .and. color(i+1,j) > 0.0) then
57830 color(i,j) = color(i+1,j)
57840 ncount = ncount + 1
5785 endif
57860 if (color(i,j) == 0.0 .and. color(i,j-1) > 0.0) then
57870 color(i,j) = color(i,j-1)
57880 ncount = ncount + 1
5789 endif
57900 if (color(i,j) == 0.0 .and. color(i,j+1) > 0.0) then
57910 color(i,j) = color(i,j+1)
57920 ncount = ncount + 1
5793 endif
5794 enddo
5795 enddo
57960 call pass_var(color, G%Domain)
57970 call sum_across_PEs(ncount)
5798 enddo
5799
58000end subroutine flood_fill2
5801
5802!> Register OBC segment data for restarts
58030subroutine open_boundary_register_restarts(HI, GV, US, OBC, Reg, param_file, restart_CS, &
5804 use_temperature)
5805 type(hor_index_type), intent(in) :: HI !< Horizontal indices
5806 type(verticalGrid_type), pointer :: GV !< Container for vertical grid information
5807 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
5808 type(ocean_OBC_type), pointer :: OBC !< OBC data structure, data intent(inout)
5809 type(tracer_registry_type), pointer :: Reg !< pointer to tracer registry
5810 type(param_file_type), intent(in) :: param_file !< Parameter file handle
5811 type(MOM_restart_CS), intent(inout) :: restart_CS !< MOM restart control structure
5812 logical, intent(in) :: use_temperature !< If true, T and S are used
5813 ! Local variables
58140 type(vardesc) :: vd(2)
5815 integer :: m
5816 character(len=100) :: mesg, var_name
5817
58180 if (.not. associated(OBC)) &
5819 call MOM_error(FATAL, "open_boundary_register_restarts: Called with "//&
58200 "uninitialized OBC control structure")
5821
5822 ! ### This is a temporary work around for restarts with OBC segments.
5823 ! This implementation uses 3D arrays solely for restarts. We need
5824 ! to be able to add 2D ( x,z or y,z ) data to restarts to avoid using
5825 ! so much memory and disk space.
58260 if (OBC%radiation_BCs_exist_globally) then
58270 allocate(OBC%rx_normal(HI%isdB:HI%iedB,HI%jsd:HI%jed,GV%ke), source=0.0)
58280 allocate(OBC%ry_normal(HI%isd:HI%ied,HI%jsdB:HI%jedB,GV%ke), source=0.0)
5829
58300 vd(1) = var_desc("rx_normal", "gridpoint timestep-1", "Normal Phase Speed for EW radiation OBCs", 'u', 'L')
58310 vd(2) = var_desc("ry_normal", "gridpoint timestep-1", "Normal Phase Speed for NS radiation OBCs", 'v', 'L')
58320 call register_restart_pair(OBC%rx_normal, OBC%ry_normal, vd(1), vd(2), .false., restart_CS, scalar_pair=.true.)
5833 ! The rx_normal and ry_normal arrays used with radiation OBCs are currently in units of grid
5834 ! points per timestep, but if this were to be corrected to [L T-1 ~> m s-1] or [T-1 ~> s-1] to
5835 ! permit timesteps to change between calls to the OBC code, the following would be needed instead:
5836 ! vd(1) = var_desc("rx_normal", "m s-1", "Normal Phase Speed for EW radiation OBCs", 'u', 'L')
5837 ! vd(2) = var_desc("ry_normal", "m s-1", "Normal Phase Speed for NS radiation OBCs", 'v', 'L')
5838 ! call register_restart_pair(OBC%rx_normal, OBC%ry_normal, vd(1), vd(2), .false., restart_CS, &
5839 ! conversion=US%L_T_to_m_s, scalar_pair=.true.)
5840 endif
5841
58420 if (OBC%oblique_BCs_exist_globally) then
58430 allocate(OBC%rx_oblique_u(HI%isdB:HI%iedB,HI%jsd:HI%jed,GV%ke), source=0.0)
58440 allocate(OBC%ry_oblique_u(HI%isdB:HI%iedB,HI%jsd:HI%jed,GV%ke), source=0.0)
58450 allocate(OBC%cff_normal_u(HI%IsdB:HI%IedB,HI%jsd:HI%jed,GV%ke), source=0.0)
58460 allocate(OBC%rx_oblique_v(HI%isd:HI%ied,HI%jsdB:HI%jedB,GV%ke), source=0.0)
58470 allocate(OBC%ry_oblique_v(HI%isd:HI%ied,HI%jsdB:HI%jedB,GV%ke), source=0.0)
58480 allocate(OBC%cff_normal_v(HI%isd:HI%ied,HI%jsdB:HI%jedB,GV%ke), source=0.0)
5849
58500 vd(1) = var_desc("rx_oblique_u", "m2 s-2", "X-Direction Radiation Speed Squared for EW oblique OBCs", 'u', 'L')
58510 vd(2) = var_desc("ry_oblique_v", "m2 s-2", "Y-Direction Radiation Speed Squared for NS oblique OBCs", 'v', 'L')
5852 call register_restart_pair(OBC%rx_oblique_u, OBC%ry_oblique_v, vd(1), vd(2), .false., &
58530 restart_CS, conversion=US%L_T_to_m_s**2)
58540 vd(1) = var_desc("ry_oblique_u", "m2 s-2", "Y-Direction Radiation Speed Squared for EW oblique OBCs", 'u', 'L')
58550 vd(2) = var_desc("rx_oblique_v", "m2 s-2", "X-Direction Radiation Speed Squared for NS oblique OBCs", 'v', 'L')
5856 call register_restart_pair(OBC%ry_oblique_u, OBC%rx_oblique_v, vd(1), vd(2), .false., &
58570 restart_CS, conversion=US%L_T_to_m_s**2)
5858
5859 vd(1) = var_desc("norm_oblique_u", "m2 s-2", "Denominator for normalizing EW oblique OBC radiation rates", &
58600 'u', 'L')
5861 vd(2) = var_desc("norm_oblique_v", "m2 s-2", "Denominator for normalizing NS oblique OBC radiation rates", &
58620 'v', 'L')
5863 call register_restart_pair(OBC%cff_normal_u, OBC%cff_normal_v, vd(1), vd(2), .false., &
58640 restart_CS, conversion=US%L_T_to_m_s**2)
5865 endif
5866
58670 if (OBC%thickness_x_reservoirs_used) then
58680 allocate(OBC%h_res_x(HI%isdB:HI%iedB,HI%jsd:HI%jed,GV%ke), source=0.0)
58690 if (modulo(HI%turns, 2) /= 0) then
58700 write(var_name,'("h_res_y")')
5871 call register_restart_field(OBC%h_res_x(:,:,:), var_name, .false., restart_CS, &
58720 longname="Layer thickness for NS OBCs", units="Conc", hor_grid='v')
5873 else
58740 write(var_name,'("h_res_x")')
5875 call register_restart_field(OBC%h_res_x(:,:,:), var_name, .false., restart_CS, &
58760 longname="Layer thickness for EW OBCs", units="Conc", hor_grid='u')
5877 endif
5878 endif
58790 if (OBC%thickness_y_reservoirs_used) then
58800 allocate(OBC%h_res_y(HI%isd:HI%ied,HI%jsdB:HI%jedB,GV%ke), source=0.0)
58810 if (modulo(HI%turns, 2) /= 0) then
58820 write(var_name,'("h_res_x")')
5883 call register_restart_field(OBC%h_res_y(:,:,:), var_name, .false., restart_CS, &
58840 longname="Layer thickness for EW OBCs", units="Conc", hor_grid='u')
5885 else
58860 write(var_name,'("h_res_y")')
5887 call register_restart_field(OBC%h_res_y(:,:,:), var_name, .false., restart_CS, &
58880 longname="Layer thickness for NS OBCs", units="Conc", hor_grid='v')
5889 endif
5890 endif
5891
58920 if (Reg%ntr == 0) return
58930 if (.not. allocated(OBC%tracer_x_reservoirs_used)) then
58940 OBC%ntr = Reg%ntr
58950 allocate(OBC%tracer_x_reservoirs_used(Reg%ntr), source=.false.)
58960 allocate(OBC%tracer_y_reservoirs_used(Reg%ntr), source=.false.)
58970 call parse_for_tracer_reservoirs(OBC, param_file, use_temperature)
5898 else
5899 ! This would be coming from user code such as DOME.
59000 if (OBC%ntr /= Reg%ntr) then
5901! call MOM_error(FATAL, "open_boundary_register_restarts: Inconsistent value for ntr")
59020 write(mesg,'("Inconsistent values for ntr ", I0," and ",I0,".")') OBC%ntr, Reg%ntr
59030 call MOM_error(WARNING, 'open_boundary_register_restarts: '//mesg)
5904 endif
5905 endif
5906
5907 ! Still painfully inefficient, now in four dimensions.
59080 if (any(OBC%tracer_x_reservoirs_used)) then
59090 allocate(OBC%tres_x(HI%isdB:HI%iedB,HI%jsd:HI%jed,GV%ke,OBC%ntr), source=0.0)
59100 do m=1,OBC%ntr
59110 if (OBC%tracer_x_reservoirs_used(m)) then
59120 if (modulo(HI%turns, 2) /= 0) then
59130 write(var_name,'("tres_y_",I3.3)') m
5914 call register_restart_field(OBC%tres_x(:,:,:,m), var_name, .false., restart_CS, &
59150 longname="Tracer concentration for NS OBCs", units="Conc", hor_grid='v')
5916 else
59170 write(var_name,'("tres_x_",I3.3)') m
5918 call register_restart_field(OBC%tres_x(:,:,:,m), var_name, .false., restart_CS, &
59190 longname="Tracer concentration for EW OBCs", units="Conc", hor_grid='u')
5920 endif
5921 endif
5922 enddo
5923 endif
59240 if (any(OBC%tracer_y_reservoirs_used)) then
59250 allocate(OBC%tres_y(HI%isd:HI%ied,HI%jsdB:HI%jedB,GV%ke,OBC%ntr), source=0.0)
59260 do m=1,OBC%ntr
59270 if (OBC%tracer_y_reservoirs_used(m)) then
59280 if (modulo(HI%turns, 2) /= 0) then
59290 write(var_name,'("tres_x_",I3.3)') m
5930 call register_restart_field(OBC%tres_y(:,:,:,m), var_name, .false., restart_CS, &
59310 longname="Tracer concentration for EW OBCs", units="Conc", hor_grid='u')
5932 else
59330 write(var_name,'("tres_y_",I3.3)') m
5934 call register_restart_field(OBC%tres_y(:,:,:,m), var_name, .false., restart_CS, &
59350 longname="Tracer concentration for NS OBCs", units="Conc", hor_grid='v')
5936 endif
5937 endif
5938 enddo
5939 endif
5940
5941end subroutine open_boundary_register_restarts
5942
5943!> Update the OBC tracer reservoirs after the tracers have been updated.
59440subroutine update_segment_tracer_reservoirs(G, GV, uhr, vhr, h, OBC, Reg)
5945 type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure
5946 type(verticalGrid_type), intent(in) :: GV !< Ocean vertical grid structure
5947 real, dimension(SZIB_(G),SZJ_(G),SZK_(GV)), intent(in) :: uhr !< accumulated volume/mass flux through
5948 !! the zonal face [H L2 ~> m3 or kg]
5949 real, dimension(SZI_(G),SZJB_(G),SZK_(GV)), intent(in) :: vhr !< accumulated volume/mass flux through
5950 !! the meridional face [H L2 ~> m3 or kg]
5951 real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(in) :: h !< layer thickness after advection
5952 !! [H ~> m or kg m-2]
5953 type(ocean_OBC_type), pointer :: OBC !< Open boundary structure
5954 type(tracer_registry_type), pointer :: Reg !< pointer to tracer registry
5955
5956 ! Local variable
5957 type(OBC_segment_type), pointer :: segment => NULL()
5958 real :: u_L_in, u_L_out ! The zonal distance moved in or out of a cell, normalized by the reservoir
5959 ! length scale [nondim]
5960 real :: v_L_in, v_L_out ! The meridional distance moved in or out of a cell, normalized by the reservoir
5961 ! length scale [nondim]
5962 real :: fac1 ! The denominator of the expression for tracer updates [nondim]
5963 real :: I_scale ! The inverse of the scaling factor for the tracers.
5964 ! For salinity the units would be [ppt S-1 ~> 1]
5965 integer :: i, j, k, m, n, ntr, nz, ntr_id, fd_id
5966 integer :: ishift, idir, jshift, jdir
5967 real :: resrv_lfac_out ! The reservoir inverse length scale scaling factor for the outward
5968 ! direction per field [nondim]
5969 real :: resrv_lfac_in ! The reservoir inverse length scale scaling factor for the inward
5970 ! direction per field [nondim]
5971 real :: b_in, b_out ! The 0 and 1 switch for tracer reservoirs
5972 ! 1 if the length scale of reservoir is zero [nondim]
5973 real :: a_in, a_out ! The 0 and 1(-1) switch for reservoir source weights
5974 ! e.g. a_in is -1 only if b_in ==1 and uhr or vhr is inward
5975 ! e.g. a_out is 1 only if b_out==1 and uhr or vhr is outward
5976 ! It's clear that a_in and a_out cannot be both non-zero [nondim]
59770 nz = GV%ke
59780 ntr = Reg%ntr
5979
59800 if (associated(OBC)) then ; if (OBC%OBC_pe) then ; do n=1,OBC%number_of_segments
59810 segment => OBC%segment(n)
59820 if (.not. associated(segment%tr_Reg)) cycle
59830 b_in = 0.0 ; if (segment%Tr_InvLscale_in == 0.0) b_in = 1.0
59840 b_out = 0.0 ; if (segment%Tr_InvLscale_out == 0.0) b_out = 1.0
59850 if (segment%is_E_or_W) then
59860 I = segment%HI%IsdB
59870 do j=segment%HI%jsd,segment%HI%jed
5988 ! ishift+I corresponds to the nearest interior tracer cell index
5989 ! idir switches the sign of the flow so that positive is into the reservoir
59900 if (segment%direction == OBC_DIRECTION_W) then
59910 ishift = 1 ; idir = -1
5992 else
59930 ishift = 0 ; idir = 1
5994 endif
5995 ! Can keep this or take it out, either way
59960 if (G%mask2dT(I+ishift,j) == 0.0) cycle
5997 ! Update the reservoir tracer concentration implicitly using a Backward-Euler timestep
59980 do m=1,segment%tr_Reg%ntseg
59990 ntr_id = segment%tr_Reg%Tr(m)%ntr_index
60000 fd_id = segment%tr_Reg%Tr(m)%fd_index
60010 if (fd_id == -1) then
60020 resrv_lfac_out = 1.0
60030 resrv_lfac_in = 1.0
6004 else
60050 resrv_lfac_out = segment%field(fd_id)%resrv_lfac_out
60060 resrv_lfac_in = segment%field(fd_id)%resrv_lfac_in
6007 endif
60080 I_scale = 1.0 ; if (segment%tr_Reg%Tr(m)%scale /= 0.0) I_scale = 1.0 / segment%tr_Reg%Tr(m)%scale
60090 if (allocated(segment%tr_Reg%Tr(m)%tres)) then ; do k=1,nz
6010 ! Calculate weights. Both a and u_L are nondim. Adding them together has no meaning.
6011 ! However, since they cannot be both non-zero, adding them works like a switch.
6012 ! When InvLscale_out is 0 and outflow, only interior data is applied to reservoirs
6013 ! When InvLscale_in is 0 and inflow, only nudged data is applied to reservoirs
60140 a_out = b_out * max(0.0, sign(1.0, idir*uhr(I,j,k)))
60150 a_in = b_in * min(0.0, sign(1.0, idir*uhr(I,j,k)))
6016 u_L_out = max(0.0, (idir*uhr(I,j,k))*segment%Tr_InvLscale_out*resrv_lfac_out / &
60170 ((h(i+ishift,j,k) + GV%H_subroundoff)*G%dyCu(I,j)))
6018 u_L_in = min(0.0, (idir*uhr(I,j,k))*segment%Tr_InvLscale_in*resrv_lfac_in / &
60190 ((h(i+ishift,j,k) + GV%H_subroundoff)*G%dyCu(I,j)))
60200 fac1 = (1.0 - (a_out - a_in)) + ((u_L_out + a_out) - (u_L_in + a_in))
6021 segment%tr_Reg%Tr(m)%tres(I,j,k) = (1.0/fac1) * &
6022 ((1.0-a_out+a_in)*segment%tr_Reg%Tr(m)%tres(I,j,k)+ &
6023 ((u_L_out+a_out)*Reg%Tr(ntr_id)%t(I+ishift,j,k) - &
60240 (u_L_in+a_in)*segment%tr_Reg%Tr(m)%t(I,j,k)))
60250 if (allocated(OBC%tres_x)) OBC%tres_x(I,j,k,m) = I_scale * segment%tr_Reg%Tr(m)%tres(I,j,k)
6026 enddo ; endif
6027 enddo
6028 enddo
60290 elseif (segment%is_N_or_S) then
60300 J = segment%HI%JsdB
60310 do i=segment%HI%isd,segment%HI%ied
6032 ! jshift+J corresponds to the nearest interior tracer cell index
6033 ! jdir switches the sign of the flow so that positive is into the reservoir
60340 if (segment%direction == OBC_DIRECTION_S) then
60350 jshift = 1 ; jdir = -1
6036 else
60370 jshift = 0 ; jdir = 1
6038 endif
6039 ! Can keep this or take it out, either way
60400 if (G%mask2dT(i,j+jshift) == 0.0) cycle
6041 ! Update the reservoir tracer concentration implicitly using a Backward-Euler timestep
60420 do m=1,segment%tr_Reg%ntseg
60430 ntr_id = segment%tr_Reg%Tr(m)%ntr_index
60440 fd_id = segment%tr_Reg%Tr(m)%fd_index
60450 if (fd_id == -1) then
60460 resrv_lfac_out = 1.0
60470 resrv_lfac_in = 1.0
6048 else
60490 resrv_lfac_out = segment%field(fd_id)%resrv_lfac_out
60500 resrv_lfac_in = segment%field(fd_id)%resrv_lfac_in
6051 endif
60520 I_scale = 1.0 ; if (segment%tr_Reg%Tr(m)%scale /= 0.0) I_scale = 1.0 / segment%tr_Reg%Tr(m)%scale
60530 if (allocated(segment%tr_Reg%Tr(m)%tres)) then ; do k=1,nz
60540 a_out = b_out * max(0.0, sign(1.0, jdir*vhr(i,J,k)))
60550 a_in = b_in * min(0.0, sign(1.0, jdir*vhr(i,J,k)))
6056 v_L_out = max(0.0, (jdir*vhr(i,J,k))*segment%Tr_InvLscale_out*resrv_lfac_out / &
60570 ((h(i,j+jshift,k) + GV%H_subroundoff)*G%dxCv(i,J)))
6058 v_L_in = min(0.0, (jdir*vhr(i,J,k))*segment%Tr_InvLscale_in*resrv_lfac_in / &
60590 ((h(i,j+jshift,k) + GV%H_subroundoff)*G%dxCv(i,J)))
60600 fac1 = (1.0 - (a_out - a_in)) + ((v_L_out + a_out) - (v_L_in + a_in))
6061 segment%tr_Reg%Tr(m)%tres(i,J,k) = (1.0/fac1) * &
6062 ((1.0-a_out+a_in)*segment%tr_Reg%Tr(m)%tres(i,J,k) + &
6063 ((v_L_out+a_out)*Reg%Tr(ntr_id)%t(i,J+jshift,k) - &
60640 (v_L_in+a_in)*segment%tr_Reg%Tr(m)%t(i,J,k)))
60650 if (allocated(OBC%tres_y)) OBC%tres_y(i,J,k,m) = I_scale * segment%tr_Reg%Tr(m)%tres(i,J,k)
6066 enddo ; endif
6067 enddo
6068 enddo
6069 endif
6070 enddo ; endif ; endif
6071
60720end subroutine update_segment_tracer_reservoirs
6073
6074!> Update the OBC thickness reservoirs after the thicknesses have been updated.
60750subroutine update_segment_thickness_reservoirs(G, GV, uhr, vhr, h, OBC)
6076 type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure
6077 type(verticalGrid_type), intent(in) :: GV !< Ocean vertical grid structure
6078 real, dimension(SZIB_(G),SZJ_(G),SZK_(GV)), intent(in) :: uhr !< accumulated volume/mass flux through
6079 !! the zonal face [H L2 ~> m3 or kg]
6080 real, dimension(SZI_(G),SZJB_(G),SZK_(GV)), intent(in) :: vhr !< accumulated volume/mass flux through
6081 !! the meridional face [H L2 ~> m3 or kg]
6082 real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(in) :: h !< layer thickness after advection
6083 !! [H ~> m or kg m-2]
6084 type(ocean_OBC_type), pointer :: OBC !< Open boundary structure
6085
6086 ! Local variable
6087 type(OBC_segment_type), pointer :: segment=>NULL()
6088 real :: u_L_in, u_L_out ! The zonal distance moved in or out of a cell, normalized by the reservoir
6089 ! length scale [nondim]
6090 real :: v_L_in, v_L_out ! The meridional distance moved in or out of a cell, normalized by the reservoir
6091 ! length scale [nondim]
6092 real :: fac1 ! The denominator of the expression for tracer updates [nondim]
6093 real :: I_scale ! The inverse of the scaling factor for the tracers.
6094 ! For salinity the units would be [ppt S-1 ~> 1]
6095 integer :: i, j, k, n, nz, fd_id
6096 integer :: ishift, idir, jshift, jdir
6097 real :: resrv_lfac_out ! The reservoir inverse length scale scaling factor for the outward
6098 ! direction per field [nondim]
6099 real :: resrv_lfac_in ! The reservoir inverse length scale scaling factor for the inward
6100 ! direction per field [nondim]
6101 real :: b_in, b_out ! The 0 and 1 switch for tracer reservoirs
6102 ! 1 if the length scale of reservoir is zero [nondim]
6103 real :: a_in, a_out ! The 0 and 1(-1) switch for reservoir source weights
6104 ! e.g. a_in is -1 only if b_in ==1 and uhr or vhr is inward
6105 ! e.g. a_out is 1 only if b_out==1 and uhr or vhr is outward
6106 ! It's clear that a_in and a_out cannot be both non-zero [nondim]
61070 nz = GV%ke
6108
61090 if (associated(OBC)) then ; if (OBC%OBC_pe) then ; do n=1,OBC%number_of_segments
61100 segment=>OBC%segment(n)
61110 if (.not. associated(segment%h_Reg)) cycle
61120 b_in = 0.0 ; if (segment%Tr_InvLscale_in == 0.0) b_in = 1.0
61130 b_out = 0.0 ; if (segment%Tr_InvLscale_out == 0.0) b_out = 1.0
61140 if (segment%is_E_or_W) then
61150 I = segment%HI%IsdB
61160 do j=segment%HI%jsd,segment%HI%jed
6117 ! ishift+I corresponds to the nearest interior tracer cell index
6118 ! idir switches the sign of the flow so that positive is into the reservoir
61190 if (segment%direction == OBC_DIRECTION_W) then
61200 ishift = 1 ; idir = -1
6121 else
61220 ishift = 0 ; idir = 1
6123 endif
6124 ! Can keep this or take it out, either way
61250 if (G%mask2dT(I+ishift,j) == 0.0) cycle
6126 ! Update the reservoir thickness concentration implicitly using a Backward-Euler timestep
61270 fd_id = segment%h_Reg%fd_index
61280 if (fd_id == -1) then
61290 resrv_lfac_out = 1.0
61300 resrv_lfac_in = 1.0
6131 else
61320 resrv_lfac_out = segment%field(fd_id)%resrv_lfac_out
61330 resrv_lfac_in = segment%field(fd_id)%resrv_lfac_in
6134 endif
61350 I_scale = 1.0 ; if (segment%h_Reg%scale /= 0.0) I_scale = 1.0 / segment%h_Reg%scale
61360 if (allocated(segment%h_Reg%h_res)) then ; do k=1,nz
6137 ! Calculate weights. Both a and u_L are nondim. Adding them together has no meaning.
6138 ! However, since they cannot be both non-zero, adding them works like a switch.
6139 ! When InvLscale_out is 0 and outflow, only interior data is applied to reservoirs
6140 ! When InvLscale_in is 0 and inflow, only nudged data is applied to reservoirs
61410 a_out = b_out * max(0.0, sign(1.0, idir*uhr(I,j,k)))
61420 a_in = b_in * min(0.0, sign(1.0, idir*uhr(I,j,k)))
6143 u_L_out = max(0.0, (idir*uhr(I,j,k))*segment%Th_InvLscale_out*resrv_lfac_out / &
61440 ((h(i+ishift,j,k) + GV%H_subroundoff)*G%dyCu(I,j)))
6145 u_L_in = min(0.0, (idir*uhr(I,j,k))*segment%Th_InvLscale_in*resrv_lfac_in / &
61460 ((h(i+ishift,j,k) + GV%H_subroundoff)*G%dyCu(I,j)))
61470 fac1 = (1.0 - (a_out - a_in)) + ((u_L_out + a_out) - (u_L_in + a_in))
6148 segment%h_Reg%h_res(I,j,k) = (1.0/fac1) * &
6149 ((1.0-a_out+a_in)*segment%h_Reg%h_res(I,j,k)+ &
6150 ((u_L_out+a_out)*h(i+ishift,j,k) - &
61510 (u_L_in+a_in)*segment%h_Reg%h(I,j,k)))
61520 if (allocated(OBC%h_res_x)) OBC%h_res_x(I,j,k) = I_scale * segment%h_Reg%h_res(I,j,k)
6153 enddo ; endif
6154 enddo
61550 elseif (segment%is_N_or_S) then
61560 J = segment%HI%JsdB
61570 do i=segment%HI%isd,segment%HI%ied
6158 ! jshift+J corresponds to the nearest interior tracer cell index
6159 ! jdir switches the sign of the flow so that positive is into the reservoir
61600 if (segment%direction == OBC_DIRECTION_S) then
61610 jshift = 1 ; jdir = -1
6162 else
61630 jshift = 0 ; jdir = 1
6164 endif
6165 ! Can keep this or take it out, either way
61660 if (G%mask2dT(i,j+jshift) == 0.0) cycle
6167 ! Update the reservoir tracer concentration implicitly using a Backward-Euler timestep
61680 fd_id = segment%h_Reg%fd_index
61690 if (fd_id == -1) then
61700 resrv_lfac_out = 1.0
61710 resrv_lfac_in = 1.0
6172 else
61730 resrv_lfac_out = segment%field(fd_id)%resrv_lfac_out
61740 resrv_lfac_in = segment%field(fd_id)%resrv_lfac_in
6175 endif
61760 I_scale = 1.0 ; if (segment%h_Reg%scale /= 0.0) I_scale = 1.0 / segment%h_Reg%scale
61770 if (allocated(segment%h_Reg%h_res)) then ; do k=1,nz
61780 a_out = b_out * max(0.0, sign(1.0, jdir*vhr(i,J,k)))
61790 a_in = b_in * min(0.0, sign(1.0, jdir*vhr(i,J,k)))
6180 v_L_out = max(0.0, (jdir*vhr(i,J,k))*segment%Th_InvLscale_out*resrv_lfac_out / &
61810 ((h(i,j+jshift,k) + GV%H_subroundoff)*G%dxCv(i,J)))
6182 v_L_in = min(0.0, (jdir*vhr(i,J,k))*segment%Th_InvLscale_in*resrv_lfac_in / &
61830 ((h(i,j+jshift,k) + GV%H_subroundoff)*G%dxCv(i,J)))
61840 fac1 = (1.0 - (a_out - a_in)) + ((v_L_out + a_out) - (v_L_in + a_in))
6185 segment%h_Reg%h_res(i,J,k) = (1.0/fac1) * &
6186 ((1.0-a_out+a_in)*segment%h_Reg%h_res(i,J,k) + &
6187 ((v_L_out+a_out)*h(i,j+jshift,k) - &
61880 (v_L_in+a_in)*segment%h_Reg%h(i,J,k)))
61890 if (allocated(OBC%h_res_y)) OBC%h_res_y(i,J,k) = I_scale * segment%h_Reg%h_res(i,J,k)
6190 enddo ; endif
6191 enddo
6192 endif
6193 enddo ; endif ; endif
6194
61950end subroutine update_segment_thickness_reservoirs
6196
6197!> Vertically remap the OBC tracer reservoirs and radiation rates that are filtered in time.
61980subroutine remap_OBC_fields(G, GV, h_old, h_new, OBC, PCM_cell)
6199 type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure
6200 type(verticalGrid_type), intent(in) :: GV !< Ocean vertical grid structure
6201 real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(in) :: h_old !< Thickness of source grid [H ~> m or kg m-2]
6202 real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(in) :: h_new !< Thickness of destination grid [H ~> m or kg m-2]
6203 type(ocean_OBC_type), pointer :: OBC !< Open boundary structure
6204 logical, dimension(SZI_(G),SZJ_(G),SZK_(GV)), &
6205 optional, intent(in) :: PCM_cell !< Use PCM remapping in cells where true
6206
6207 ! Local variables
6208 type(OBC_segment_type), pointer :: segment => NULL() ! A pointer to the various segments, used just for shorthand.
6209
62100 real :: tr_column(GV%ke) ! A column of updated tracer concentrations in internally scaled units.
6211 ! For salinity the units would be [S ~> ppt].
62120 real :: r_norm_col(GV%ke) ! A column of updated radiation rates, in grid points per timestep [nondim]
62130 real :: rxy_col(GV%ke) ! A column of updated radiation rates for oblique OBCs [L2 T-2 ~> m2 s-2]
62140 real :: h1(GV%ke) ! A column of source grid layer thicknesses [H ~> m or kg m-2]
62150 real :: h2(GV%ke) ! A column of target grid layer thicknesses [H ~> m or kg m-2]
6216 real :: I_scale ! The inverse of the scaling factor for the tracers.
6217 ! For salinity the units would be [ppt S-1 ~> 1].
62180 logical :: PCM(GV%ke) ! If true, do PCM remapping from a cell.
6219 integer :: i, j, k, m, n, ntr, nz
6220
62210 if (.not.associated(OBC)) return
6222
62230 nz = GV%ke
62240 ntr = OBC%ntr
6225
62260 if (.not.present(PCM_cell)) PCM(:) = .false.
6227
62280 if (associated(OBC)) then ; if (OBC%OBC_pe) then ; do n=1,OBC%number_of_segments
62290 segment => OBC%segment(n)
62300 if (.not.associated(segment%tr_Reg)) cycle
6231
62320 if (segment%is_E_or_W) then
62330 I = segment%HI%IsdB
62340 do j=segment%HI%jsd,segment%HI%jed
6235
6236 ! Store a column of the start and final grids
62370 if (segment%direction == OBC_DIRECTION_W) then
62380 if (G%mask2dT(i+1,j) == 0.0) cycle
62390 h1(:) = h_old(i+1,j,:)
62400 h2(:) = h_new(i+1,j,:)
62410 if (present(PCM_cell)) then ; PCM(:) = PCM_cell(i+1,j,:) ; endif
6242 else
62430 if (G%mask2dT(i,j) == 0.0) cycle
62440 h1(:) = h_old(i,j,:)
62450 h2(:) = h_new(i,j,:)
62460 if (present(PCM_cell)) then ; PCM(:) = PCM_cell(i,j,:) ; endif
6247 endif
6248
6249 ! Vertically remap the reservoir tracer concentrations
62500 do m=1,ntr ; if (allocated(segment%tr_Reg%Tr(m)%tres)) then
62510 I_scale = 1.0 ; if (segment%tr_Reg%Tr(m)%scale /= 0.0) I_scale = 1.0 / segment%tr_Reg%Tr(m)%scale
6252
62530 if (present(PCM_cell)) then
6254 call remapping_core_h(OBC%remap_h_CS, nz, h1, segment%tr_Reg%Tr(m)%tres(I,j,:), nz, h2, tr_column, &
62550 PCM_cell=PCM)
6256 else
62570 call remapping_core_h(OBC%remap_h_CS, nz, h1, segment%tr_Reg%Tr(m)%tres(I,j,:), nz, h2, tr_column)
6258 endif
6259
6260 ! Possibly underflow any very tiny tracer concentrations to 0?
6261
6262 ! Update tracer concentrations
62630 segment%tr_Reg%Tr(m)%tres(I,j,:) = tr_column(:)
62640 if (allocated(OBC%tres_x)) then ; do k=1,nz
62650 OBC%tres_x(I,j,k,m) = I_scale * segment%tr_Reg%Tr(m)%tres(I,j,k)
6266 enddo ; endif
6267
6268 endif ; enddo
6269
6270 ! Vertically remap the reservoir thicknesses?
62710 if (associated(segment%h_Reg)) then
62720 if (allocated(segment%h_Reg%h_res)) then
62730 I_scale = 1.0 ; if (segment%h_Reg%scale /= 0.0) I_scale = 1.0 / segment%h_Reg%scale
6274
62750 if (present(PCM_cell)) then
6276 call remapping_core_h(OBC%remap_h_CS, nz, h1, segment%h_Reg%h_res(I,j,:), nz, h2, tr_column, &
62770 PCM_cell=PCM)
6278 else
62790 call remapping_core_h(OBC%remap_h_CS, nz, h1, segment%h_Reg%h_res(I,j,:), nz, h2, tr_column)
6280 endif
6281
6282 ! Possibly underflow any very tiny tracer concentrations to 0?
6283
6284 ! Update tracer concentrations
62850 segment%h_Reg%h_res(I,j,:) = tr_column(:)
62860 if (allocated(OBC%h_res_x)) then ; do k=1,nz
62870 OBC%h_res_x(I,j,k) = I_scale * segment%h_Reg%h_res(I,j,k)
6288 enddo ; endif
6289 endif
6290 endif
6291
62920 if (segment%radiation .and. (OBC%gamma_uv < 1.0)) then
6293 call remapping_core_h(OBC%remap_h_CS, nz, h1, segment%rx_norm_rad(I,j,:), nz, h2, r_norm_col, &
62940 PCM_cell=PCM)
6295
62960 do k=1,nz
62970 segment%rx_norm_rad(I,j,k) = r_norm_col(k)
62980 OBC%rx_normal(I,j,k) = segment%rx_norm_rad(I,j,k)
6299 enddo
6300 endif
6301
63020 if (segment%oblique .and. (OBC%gamma_uv < 1.0)) then
6303 call remapping_core_h(OBC%remap_h_CS, nz, h1, segment%rx_norm_obl(I,j,:), nz, h2, rxy_col, &
63040 PCM_cell=PCM)
63050 segment%rx_norm_obl(I,j,:) = rxy_col(:)
6306 call remapping_core_h(OBC%remap_h_CS, nz, h1, segment%ry_norm_obl(I,j,:), nz, h2, rxy_col, &
63070 PCM_cell=PCM)
63080 segment%ry_norm_obl(I,j,:) = rxy_col(:)
6309 call remapping_core_h(OBC%remap_h_CS, nz, h1, segment%cff_normal(I,j,:), nz, h2, rxy_col, &
63100 PCM_cell=PCM)
63110 segment%cff_normal(I,j,:) = rxy_col(:)
6312
63130 do k=1,nz
63140 OBC%rx_oblique_u(I,j,k) = segment%rx_norm_obl(I,j,k)
63150 OBC%ry_oblique_u(I,j,k) = segment%ry_norm_obl(I,j,k)
63160 OBC%cff_normal_u(I,j,k) = segment%cff_normal(I,j,k)
6317 enddo
6318 endif
6319
6320 enddo
63210 elseif (segment%is_N_or_S) then
63220 J = segment%HI%JsdB
63230 do i=segment%HI%isd,segment%HI%ied
6324
6325 ! Store a column of the start and final grids
63260 if (segment%direction == OBC_DIRECTION_S) then
63270 if (G%mask2dT(i,j+1) == 0.0) cycle
63280 h1(:) = h_old(i,j+1,:)
63290 h2(:) = h_new(i,j+1,:)
63300 if (present(PCM_cell)) then ; PCM(:) = PCM_cell(i,j+1,:) ; endif
6331 else
63320 if (G%mask2dT(i,j) == 0.0) cycle
63330 h1(:) = h_old(i,j,:)
63340 h2(:) = h_new(i,j,:)
63350 if (present(PCM_cell)) then ; PCM(:) = PCM_cell(i,j,:) ; endif
6336 endif
6337
6338 ! Vertically remap the reservoir tracer concentrations
63390 do m=1,ntr ; if (allocated(segment%tr_Reg%Tr(m)%tres)) then
63400 I_scale = 1.0 ; if (segment%tr_Reg%Tr(m)%scale /= 0.0) I_scale = 1.0 / segment%tr_Reg%Tr(m)%scale
6341
63420 if (present(PCM_cell)) then
6343 call remapping_core_h(OBC%remap_h_CS, nz, h1, segment%tr_Reg%Tr(m)%tres(i,J,:), nz, h2, tr_column, &
63440 PCM_cell=PCM)
6345 else
63460 call remapping_core_h(OBC%remap_h_CS, nz, h1, segment%tr_Reg%Tr(m)%tres(i,J,:), nz, h2, tr_column)
6347 endif
6348
6349 ! Possibly underflow any very tiny tracer concentrations to 0?
6350
6351 ! Update tracer concentrations
63520 segment%tr_Reg%Tr(m)%tres(i,J,:) = tr_column(:)
63530 if (allocated(OBC%tres_y)) then ; do k=1,nz
63540 OBC%tres_y(i,J,k,m) = I_scale * segment%tr_Reg%Tr(m)%tres(i,J,k)
6355 enddo ; endif
6356
6357 endif ; enddo
6358
6359 ! Vertically remap the reservoir thicknesses?
63600 if (associated(segment%h_Reg)) then
63610 if (allocated(segment%h_Reg%h_res)) then
63620 I_scale = 1.0 ; if (segment%h_Reg%scale /= 0.0) I_scale = 1.0 / segment%h_Reg%scale
6363
63640 if (present(PCM_cell)) then
6365 call remapping_core_h(OBC%remap_h_CS, nz, h1, segment%h_Reg%h_res(i,J,:), nz, h2, tr_column, &
63660 PCM_cell=PCM)
6367 else
63680 call remapping_core_h(OBC%remap_h_CS, nz, h1, segment%h_Reg%h_res(i,J,:), nz, h2, tr_column)
6369 endif
6370
6371 ! Possibly underflow any very tiny tracer concentrations to 0?
6372
6373 ! Update tracer concentrations
63740 segment%h_Reg%h_res(i,J,:) = tr_column(:)
63750 if (allocated(OBC%h_res_y)) then ; do k=1,nz
63760 OBC%h_res_y(i,J,k) = I_scale * segment%h_Reg%h_res(i,J,k)
6377 enddo ; endif
6378 endif
6379 endif
6380
63810 if (segment%radiation .and. (OBC%gamma_uv < 1.0)) then
6382 call remapping_core_h(OBC%remap_h_CS, nz, h1, segment%ry_norm_rad(i,J,:), nz, h2, r_norm_col, &
63830 PCM_cell=PCM)
6384
63850 do k=1,nz
63860 segment%ry_norm_rad(i,J,k) = r_norm_col(k)
63870 OBC%ry_normal(i,J,k) = segment%ry_norm_rad(i,J,k)
6388 enddo
6389 endif
6390
63910 if (segment%oblique .and. (OBC%gamma_uv < 1.0)) then
6392 call remapping_core_h(OBC%remap_h_CS, nz, h1, segment%rx_norm_obl(i,J,:), nz, h2, rxy_col, &
63930 PCM_cell=PCM)
63940 segment%rx_norm_obl(i,J,:) = rxy_col(:)
6395 call remapping_core_h(OBC%remap_h_CS, nz, h1, segment%ry_norm_obl(i,J,:), nz, h2, rxy_col, &
63960 PCM_cell=PCM)
63970 segment%ry_norm_obl(i,J,:) = rxy_col(:)
6398 call remapping_core_h(OBC%remap_h_CS, nz, h1, segment%cff_normal(i,J,:), nz, h2, rxy_col, &
63990 PCM_cell=PCM)
64000 segment%cff_normal(i,J,:) = rxy_col(:)
6401
64020 do k=1,nz
64030 OBC%rx_oblique_v(i,J,k) = segment%rx_norm_obl(i,J,k)
64040 OBC%ry_oblique_v(i,J,k) = segment%ry_norm_obl(i,J,k)
64050 OBC%cff_normal_v(i,J,k) = segment%cff_normal(i,J,k)
6406 enddo
6407 endif
6408
6409 enddo
6410 endif
6411 enddo ; endif ; endif
64120 if (OBC%radiation_BCs_exist_globally) call pass_vector(OBC%rx_normal, OBC%ry_normal, G%Domain, &
64130 To_All+Scalar_Pair)
64140 if (OBC%oblique_BCs_exist_globally) then
64150 call do_group_pass(OBC%pass_oblique, G%Domain)
6416 endif
6417
64180end subroutine remap_OBC_fields
6419
6420
6421!> Adjust interface heights to fit the bathymetry and diagnose layer thickness.
6422!!
6423!! If the bottom most interface is below the topography then the bottom-most
6424!! layers are contracted to GV%Angstrom_Z.
6425!! If the bottom most interface is above the topography then the entire column
6426!! is dilated (expanded) to fill the void.
6427!! @remark{There is a (hard-wired) "tolerance" parameter such that the
6428!! criteria for adjustment must equal or exceed 10cm.}
64290subroutine adjustSegmentEtaToFitBathymetry(G, GV, US, segment, fld, at_node)
6430 type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure
6431 type(verticalGrid_type), intent(in) :: GV !< The ocean's vertical grid structure
6432 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
6433 type(OBC_segment_type), intent(inout) :: segment !< OBC segment
6434 integer, intent(in) :: fld !< field index to adjust thickness
6435 logical, intent(in) :: at_node !< True this point is at the OBC nodes rather than the faces
6436
6437 integer :: i, j, k, is, ie, js, je, nz, contractions, dilations
64380 real, allocatable, dimension(:,:,:) :: eta ! Segment source data interface heights [Z ~> m]
64390 real, allocatable, dimension(:,:) :: dz_tot ! Segment total thicknesses [Z ~> m]
6440 real :: hTolerance = 0.1 !< Tolerance to exceed adjustment criteria [Z ~> m]
6441 ! real :: dilate ! A factor by which to dilate the water column [nondim]
6442 !character(len=100) :: mesg
6443
64440 hTolerance = 0.1*US%m_to_Z
6445
64460 nz = size(segment%field(fld)%dz_src,3)
6447
64480 if (segment%is_E_or_W) then
64490 is = segment%HI%IsdB ; ie = segment%HI%IedB
64500 if (at_node) then ! This point is at the OBC nodes, rather than the cell face centers.
64510 Js = max(segment%Js_obc, G%jsd)
64520 Je = min(segment%Je_obc, G%jed-1)
6453 else ! Segment thicknesses are defined at cell face centers.
64540 js = segment%HI%jsd ; je = segment%HI%jed
6455 endif
6456 else ! segment%is_N_or_S
64570 js = segment%HI%jsdB ; je = segment%HI%jedB
64580 if (at_node) then ! This point is at the OBC nodes, rather than the cell face centers.
64590 is = max(segment%HI%IsdB, G%isd)
64600 ie = min(segment%HI%IedB, G%ied-1)
6461 else ! Segment thicknesses are defined at cell face centers.
64620 is = segment%HI%isd ; ie = segment%HI%ied
6463 endif
6464 endif
64650 allocate(eta(is:ie,js:je,nz+1))
64660 allocate(dz_tot(is:ie,js:je), source=0.0)
6467
64680 if (at_node) then
64690 if (segment%is_E_or_W) then
64700 I = Is
64710 do J=Js,Je
64720 dz_tot(I,J) = 0.5*(segment%dZtot(I,j) + segment%dZtot(I,j+1))
6473 enddo
6474 ! Do not extrapolate past the end of a global segment.
6475 ! ### For a concave corner between segments, perhaps we should do something more sophisticated.
64760 if (Js == segment%Js_obc) dz_tot(I,Js) = segment%dZtot(I,js+1)
64770 if (Je == segment%Js_obc) dz_tot(I,Je) = segment%dZtot(I,je)
6478 else
64790 J = Js
64800 do I=Is,Ie
64810 dz_tot(I,J) = 0.5*(segment%dZtot(i,J) + segment%dZtot(i+1,J))
6482 enddo
6483 ! Do not extrapolate past the end of a global segment.
64840 if (Is == segment%Is_obc) dz_tot(Is,J) = segment%dZtot(is+1,J)
64850 if (Ie == segment%Is_obc) dz_tot(Ie,J) = segment%dZtot(ie,J)
6486 endif
6487 else
64880 do j=js,je ; do i=is,ie
64890 dz_tot(i,j) = segment%dZtot(i,j)
6490 enddo ; enddo
6491 endif
6492
64930 contractions = 0 ; dilations = 0
64940 do j=js,je ; do i=is,ie
64950 eta(i,j,1) = 0.0 ! segment data are assumed to be located on a static grid
6496 ! For remapping calls, the entire column will be dilated
6497 ! by a factor equal to the ratio of the sum of the geopotential referenced
6498 ! source data thicknesses, and the current model thicknesses. This could be
6499 ! an issue to be addressed, for instance if we are placing open boundaries
6500 ! under ice shelf cavities.
65010 do k=2,nz+1
65020 eta(i,j,k) = eta(i,j,k-1) - segment%field(fld)%dz_src(i,j,k-1)
6503 enddo
6504 ! The normal slope at the boundary is zero by a
6505 ! previous call to open_boundary_impose_normal_slope
65060 do k=nz+1,1,-1
65070 if (-eta(i,j,k) > dz_tot(i,j) + hTolerance) then
65080 eta(i,j,k) = -dz_tot(i,j)
65090 contractions = contractions + 1
6510 endif
6511 enddo
6512
65130 do k=1,nz
6514 ! Collapse layers to thinnest possible if the thickness less than
6515 ! the thinnest possible (or negative).
65160 if (eta(i,j,K) < (eta(i,j,K+1) + GV%Angstrom_Z)) then
65170 eta(i,j,K) = eta(i,j,K+1) + GV%Angstrom_Z
65180 segment%field(fld)%dz_src(i,j,k) = GV%Angstrom_Z
6519 else
65200 segment%field(fld)%dz_src(i,j,k) = (eta(i,j,K) - eta(i,j,K+1))
6521 endif
6522 enddo
6523
6524 ! The whole column is dilated to accommodate deeper topography than
6525 ! the bathymetry would indicate.
65260 if (-eta(i,j,nz+1) < dz_tot(i,j) - hTolerance) then
65270 dilations = dilations + 1
6528 ! expand bottom-most cell only
65290 eta(i,j,nz+1) = -dz_tot(i,j)
65300 segment%field(fld)%dz_src(i,j,nz) = eta(i,j,nz) - eta(i,j,nz+1)
6531 ! if (eta(i,j,1) <= eta(i,j,nz+1)) then
6532 ! do k=1,nz ; segment%field(fld)%dz_src(i,j,k) = (eta(i,j,1) + G%bathyT(i,j)) / real(nz) ; enddo
6533 ! else
6534 ! dilate = (eta(i,j,1) + G%bathyT(i,j)) / (eta(i,j,1) - eta(i,j,nz+1))
6535 ! do k=1,nz ; segment%field(fld)%dz_src(i,j,k) = segment%field(fld)%dz_src(i,j,k) * dilate ; enddo
6536 ! endif
6537 !do k=nz,2,-1 ; eta(i,j,K) = eta(i,j,K+1) + segment%field(fld)%dz_src(i,j,k) ; enddo
6538 endif
6539 enddo ; enddo
6540
6541 ! can not do communication call here since only PEs on the current segment are here
6542 ! call sum_across_PEs(contractions)
6543 ! if ((contractions > 0) .and. (is_root_pe())) then
6544 ! write(mesg,'("Thickness OBCs were contracted ",'// &
6545 ! '"to fit topography in ",I0," places.")') contractions
6546 ! call MOM_error(WARNING, 'adjustEtaToFitBathymetry: '//mesg)
6547 ! endif
6548 ! call sum_across_PEs(dilations)
6549 ! if ((dilations > 0) .and. (is_root_pe())) then
6550 ! write(mesg,'("Thickness OBCs were dilated ",'// &
6551 ! '"to fit topography in ",I0," places.")') dilations
6552 ! call MOM_error(WARNING, 'adjustEtaToFitBathymetry: '//mesg)
6553 ! endif
6554
65550 deallocate(eta, dz_tot)
6556
65570end subroutine adjustSegmentEtaToFitBathymetry
6558
6559!> This is more of a rotate initialization than an actual rotate
65600subroutine rotate_OBC_config(OBC_in, G_in, OBC, G, turns)
6561 type(ocean_OBC_type), pointer, intent(in) :: OBC_in !< Input OBC
6562 type(dyn_horgrid_type), intent(in) :: G_in !< Input grid
6563 type(ocean_OBC_type), pointer, intent(inout) :: OBC !< Rotated OBC
6564 type(dyn_horgrid_type), intent(in) :: G !< Rotated grid
6565 integer, intent(in) :: turns !< Number of quarter turns
6566
6567 integer :: c, n, l_seg
6568
65690 if (OBC_in%number_of_segments == 0) return
6570
6571 ! Scalar and logical transfer
65720 OBC%number_of_segments = OBC_in%number_of_segments
65730 OBC%ke = OBC_in%ke
65740 OBC%user_BCs_set_globally = OBC_in%user_BCs_set_globally
6575
6576 ! These are conditionally read and set if number_of_segments > 0
65770 OBC%vorticity_config = OBC_in%vorticity_config
65780 OBC%strain_config = OBC_in%strain_config
65790 OBC%zero_biharmonic = OBC_in%zero_biharmonic
65800 OBC%silly_h = OBC_in%silly_h
65810 OBC%silly_u = OBC_in%silly_u
65820 OBC%reverse_segment_order = OBC_in%reverse_segment_order
6583
6584 ! Segment rotation
65850 allocate(OBC%segment(0:OBC%number_of_segments))
65860 do l_seg=1,OBC%number_of_segments
65870 call rotate_OBC_segment_config(OBC_in%segment(l_seg), G_in, OBC%segment(l_seg), G, turns)
6588 ! Data stored in setup_[uv]_point_obc is needed for allocate_obc_segment_data
65890 call allocate_OBC_segment_data(OBC, OBC%segment(l_seg))
6590 enddo
6591
6592 ! The horizontal segment map
65930 allocate(OBC%segnum_u(G%IsdB:G%IedB,G%jsd:G%jed), source=0)
65940 allocate(OBC%segnum_v(G%isd:G%ied,G%JsdB:G%JedB), source=0)
65950 call rotate_array_pair(OBC_in%segnum_u, OBC_in%segnum_v, turns, OBC%segnum_u, OBC%segnum_v)
65960 call set_segnum_signs(OBC, G)
6597
6598 ! These are conditionally enabled during segment configuration
65990 if (modulo(turns,2) == 0) then
66000 OBC%open_u_BCs_exist_globally = OBC_in%open_u_BCs_exist_globally
66010 OBC%open_v_BCs_exist_globally = OBC_in%open_v_BCs_exist_globally
66020 OBC%Flather_u_BCs_exist_globally = OBC_in%Flather_u_BCs_exist_globally
66030 OBC%Flather_v_BCs_exist_globally = OBC_in%Flather_v_BCs_exist_globally
66040 OBC%nudged_u_BCs_exist_globally = OBC_in%nudged_u_BCs_exist_globally
66050 OBC%nudged_v_BCs_exist_globally = OBC_in%nudged_v_BCs_exist_globally
66060 OBC%specified_u_BCs_exist_globally = OBC_in%specified_u_BCs_exist_globally
66070 OBC%specified_v_BCs_exist_globally = OBC_in%specified_v_BCs_exist_globally
6608 else ! Swap information for u- and v- OBCs
66090 OBC%open_u_BCs_exist_globally = OBC_in%open_v_BCs_exist_globally
66100 OBC%open_v_BCs_exist_globally = OBC_in%open_u_BCs_exist_globally
66110 OBC%Flather_u_BCs_exist_globally = OBC_in%Flather_v_BCs_exist_globally
66120 OBC%Flather_v_BCs_exist_globally = OBC_in%Flather_u_BCs_exist_globally
66130 OBC%nudged_u_BCs_exist_globally = OBC_in%nudged_v_BCs_exist_globally
66140 OBC%nudged_v_BCs_exist_globally = OBC_in%nudged_u_BCs_exist_globally
66150 OBC%specified_u_BCs_exist_globally = OBC_in%specified_v_BCs_exist_globally
66160 OBC%specified_v_BCs_exist_globally = OBC_in%specified_u_BCs_exist_globally
6617 endif
66180 OBC%oblique_BCs_exist_globally = OBC_in%oblique_BCs_exist_globally
66190 OBC%radiation_BCs_exist_globally = OBC_in%radiation_BCs_exist_globally
6620
6621 ! These are set by initialize_segment_data
66220 OBC%brushcutter_mode = OBC_in%brushcutter_mode
66230 OBC%update_OBC = OBC_in%update_OBC
66240 OBC%any_needs_IO_for_data = OBC_in%any_needs_IO_for_data
6625
66260 OBC%update_OBC_seg_data = OBC_in%update_OBC_seg_data
66270 OBC%ntr = OBC_in%ntr
66280 if (OBC%ntr > 0) then
66290 allocate(OBC%tracer_x_reservoirs_used(OBC%ntr), source=.false.)
66300 allocate(OBC%tracer_y_reservoirs_used(OBC%ntr), source=.false.)
66310 if (modulo(turns,2) == 0) then
66320 do n=1,OBC%ntr
66330 OBC%tracer_x_reservoirs_used(n) = OBC_in%tracer_x_reservoirs_used(n)
66340 OBC%tracer_y_reservoirs_used(n) = OBC_in%tracer_y_reservoirs_used(n)
6635 enddo
6636 else ! Swap information for u- and v- OBCs
66370 do n=1,OBC%ntr
66380 OBC%tracer_x_reservoirs_used(n) = OBC_in%tracer_y_reservoirs_used(n)
66390 OBC%tracer_y_reservoirs_used(n) = OBC_in%tracer_x_reservoirs_used(n)
6640 enddo
6641 endif
6642 endif
6643
66440 OBC%gamma_uv = OBC_in%gamma_uv
66450 OBC%rx_max = OBC_in%rx_max
66460 OBC%OBC_pe = OBC_in%OBC_pe
6647
6648 ! These are run-time parameters that are read in via open_boundary_config
66490 OBC%debug = OBC_in%debug
66500 OBC%ramp = OBC_in%ramp
66510 OBC%ramping_is_activated = OBC_in%ramping_is_activated
66520 OBC%ramp_timescale = OBC_in%ramp_timescale
66530 OBC%trunc_ramp_time = OBC_in%trunc_ramp_time
66540 OBC%ramp_value = OBC_in%ramp_value
66550 OBC%ramp_start_time = OBC_in%ramp_start_time
66560 OBC%remap_answer_date = OBC_in%remap_answer_date
66570 OBC%check_reconstruction = OBC_in%check_reconstruction
66580 OBC%check_remapping = OBC_in%check_remapping
66590 OBC%force_bounds_in_subcell = OBC_in%force_bounds_in_subcell
66600 OBC%om4_remap_via_sub_cells = OBC_in%om4_remap_via_sub_cells
66610 OBC%remappingScheme = OBC_in%remappingScheme
66620 OBC%exterior_OBC_bug = OBC_in%exterior_OBC_bug
66630 OBC%hor_index_bug = OBC_in%hor_index_bug
66640 OBC%n_tide_constituents = OBC_in%n_tide_constituents
66650 OBC%add_tide_constituents = OBC_in%add_tide_constituents
6666
6667 ! These are read in via initialize_obc_tides when n_tide_constituents > 0
66680 if (OBC%add_tide_constituents .and. (OBC%n_tide_constituents>0)) then
66690 OBC%add_eq_phase = OBC_in%add_eq_phase
66700 OBC%add_nodal_terms = OBC_in%add_nodal_terms
66710 OBC%time_ref = OBC_in%time_ref
6672
66730 allocate(OBC%tide_names(OBC%n_tide_constituents))
66740 allocate(OBC%tide_frequencies(OBC%n_tide_constituents))
66750 allocate(OBC%tide_eq_phases(OBC%n_tide_constituents))
66760 allocate(OBC%tide_fn(OBC%n_tide_constituents))
66770 allocate(OBC%tide_un(OBC%n_tide_constituents))
66780 do c=1,OBC%n_tide_constituents
66790 OBC%tide_names(c) = OBC_in%tide_names(c)
66800 OBC%tide_frequencies(c) = OBC_in%tide_frequencies(c)
66810 OBC%tide_eq_phases(c) = OBC_in%tide_eq_phases(c)
66820 OBC%tide_fn(c) = OBC_in%tide_fn(c)
66830 OBC%tide_un(c) = OBC_in%tide_un(c)
6684 enddo
6685
66860 if (OBC%add_eq_phase .or. OBC%add_nodal_terms) &
66870 OBC%tidal_longitudes = OBC_in%tidal_longitudes
6688 endif
6689
6690end subroutine rotate_OBC_config
6691
6692!> Rotate the OBC segment configuration data from the input to model index map.
66930subroutine rotate_OBC_segment_config(segment_in, G_in, segment, G, turns)
6694 type(OBC_segment_type), intent(in) :: segment_in !< Input OBC segment
6695 type(dyn_horgrid_type), intent(in) :: G_in !< Input grid metric
6696 type(OBC_segment_type), intent(inout) :: segment !< Rotated OBC segment
6697 type(dyn_horgrid_type), intent(in) :: G !< Rotated grid metric
6698 integer, intent(in) :: turns !< Number of quarter turns
6699
6700 ! Global segment indices
6701 integer :: Is_obc_in, Ie_obc_in, Js_obc_in, Je_obc_in ! Input domain global indices
6702 integer :: Is_obc, Ie_obc, Js_obc, Je_obc ! Rotated domain global indices
6703 integer :: qturns ! The number of quarter turns in the range of 0 to 3
6704
6705 ! NOTE: A "rotation" of the OBC segment string would allow us to use
6706 ! setup_[uv]_point_obc to set up most of this. For now, we just copy/swap
6707 ! flags and manually rotate the indices.
6708
6709 ! This is set if the segment is in the local grid
67100 segment%on_pe = segment_in%on_pe
6711
67120 qturns = modulo(turns, 4)
6713
6714 ! Transfer configuration flags
67150 segment%Flather = segment_in%Flather
67160 segment%radiation = segment_in%radiation
67170 segment%radiation_tan = segment_in%radiation_tan
67180 segment%radiation_grad = segment_in%radiation_grad
67190 segment%oblique = segment_in%oblique
67200 segment%oblique_tan = segment_in%oblique_tan
67210 segment%oblique_grad = segment_in%oblique_grad
67220 segment%nudged = segment_in%nudged
67230 segment%nudged_tan = segment_in%nudged_tan
67240 segment%nudged_grad = segment_in%nudged_grad
67250 segment%specified = segment_in%specified
67260 segment%specified_tan = segment_in%specified_tan
67270 segment%specified_grad = segment_in%specified_grad
67280 segment%open = segment_in%open
67290 segment%gradient = segment_in%gradient
6730
6731 ! These are conditionally set if nudged
67320 segment%Velocity_nudging_timescale_in = segment_in%Velocity_nudging_timescale_in
67330 segment%Velocity_nudging_timescale_out = segment_in%Velocity_nudging_timescale_out
6734
6735 ! Rotate segment indices
6736
6737 ! Reverse engineer the input [IJ][se]_obc segment indices
6738 ! NOTE: The values stored in the segment are always saved in ascending order,
6739 ! e.g. (is < ie). In order to use setup_segment_indices, we reorder the
6740 ! indices here to indicate face direction.
6741 ! Segment indices are also indexed locally, so here we convert to global indices
67420 if (segment_in%direction == OBC_DIRECTION_N) then
67430 Is_obc_in = segment_in%Ie_obc + G_in%idg_offset
67440 Ie_obc_in = segment_in%Is_obc + G_in%idg_offset
6745 else
67460 Is_obc_in = segment_in%Is_obc + G_in%idg_offset
67470 Ie_obc_in = segment_in%Ie_obc + G_in%idg_offset
6748 endif
6749
67500 if (segment_in%direction == OBC_DIRECTION_W) then
67510 Js_obc_in = segment_in%Je_obc + G_in%jdg_offset
67520 Je_obc_in = segment_in%Js_obc + G_in%jdg_offset
6753 else
67540 Js_obc_in = segment_in%Js_obc + G_in%jdg_offset
67550 Je_obc_in = segment_in%Je_obc + G_in%jdg_offset
6756 endif
6757
6758 ! Rotate the global indices of the segment according to the number of turns.
67590 if (qturns == 0) then
67600 Is_obc = Is_obc_in ; Ie_obc = Ie_obc_in
67610 Js_obc = Js_obc_in ; Je_obc = Je_obc_in
67620 elseif (qturns == 1) then
67630 Is_obc = G_in%JegB - Js_obc_in ; Ie_obc = G_in%JegB - Je_obc_in
67640 Js_obc = Is_obc_in ; Je_obc = Ie_obc_in
67650 elseif (qturns == 2) then
67660 Is_obc = G_in%IegB - Is_obc_in ; Ie_obc = G_in%IegB - Ie_obc_in
67670 Js_obc = G_in%JegB - Js_obc_in ; Je_obc = G_in%JegB - Je_obc_in
67680 elseif (qturns == 3) then
67690 Is_obc = Js_obc_in ; Ie_obc = Je_obc_in
67700 Js_obc = G_in%IegB - Is_obc_in ; Je_obc = G_in%IegB - Ie_obc_in
6771 endif
6772
6773 ! Orientation is based on the index ordering, and setup_segment_indices
6774 ! is based on the original order in the intput files.
67750 call setup_segment_indices(G, segment, Is_obc, Ie_obc, Js_obc, Je_obc)
6776
6777 ! Re-order [IJ][se]_obc back to ascending, and remove the global indexing offset.
67780 if (Is_obc > Ie_obc) then
67790 segment%Is_obc = Ie_obc - G%idg_offset
67800 segment%Ie_obc = Is_obc - G%idg_offset
6781 else
67820 segment%Is_obc = Is_obc - G%idg_offset
67830 segment%Ie_obc = Ie_obc - G%idg_offset
6784 endif
6785
67860 if (Js_obc > Je_obc) then
67870 segment%Js_obc = Je_obc - G%jdg_offset
67880 segment%Je_obc = Js_obc - G%jdg_offset
6789 else
67900 segment%Js_obc = Js_obc - G%jdg_offset
67910 segment%Je_obc = Je_obc - G%jdg_offset
6792 endif
6793
6794 ! Reconfigure the directional flags
67950 segment%direction = rotate_OBC_segment_direction(segment_in%direction, turns)
6796
6797 segment%is_E_or_W_2 = ((segment%direction == OBC_DIRECTION_E) .or. &
67980 (segment%direction == OBC_DIRECTION_W))
67990 segment%is_E_or_W = segment_in%on_PE .and. segment%is_E_or_W_2
6800 segment%is_N_or_S = segment_in%on_PE .and. &
6801 ((segment%direction == OBC_DIRECTION_N) .or. &
68020 (segment%direction == OBC_DIRECTION_S))
6803
6804 ! These are conditionally set if Lscale_{in,out} are present
68050 segment%Tr_InvLscale_in = segment_in%Tr_InvLscale_in
68060 segment%Tr_InvLscale_out = segment_in%Tr_InvLscale_out
68070 segment%Th_InvLscale_in = segment_in%Th_InvLscale_in
68080 segment%Th_InvLscale_out = segment_in%Th_InvLscale_out
6809
6810 ! This needs to be set
68110 segment%num_fields = segment_in%num_fields
68120end subroutine rotate_OBC_segment_config
6813
6814
6815!> Return the direction of an OBC segment on after rotation to the new grid. Note that
6816!! rotate_OBC_seg_direction(rotate_OBC_seg_direction(direction, turns), -turns) = direction.
68170function rotate_OBC_segment_direction(direction, turns) result(rotated_dir)
6818 integer, intent(in) :: direction !< The orientation of an OBC segment on the original grid
6819 integer, intent(in) :: turns !< Number of quarter turns
6820 integer :: rotated_dir !< An integer encoding the new rotated segment direction
6821
6822 integer :: qturns ! The number of quarter turns in the range of 0 to 3
6823
68240 qturns = modulo(turns, 4)
6825
68260 if ((qturns == 0) .or. (direction == OBC_NONE)) then
68270 rotated_dir = direction
6828 else ! Determine the segment direction on a rotated grid
68290 select case (direction)
6830 case (OBC_DIRECTION_N)
68310 if (qturns == 0) rotated_dir = OBC_DIRECTION_N
68320 if (qturns == 1) rotated_dir = OBC_DIRECTION_W
68330 if (qturns == 2) rotated_dir = OBC_DIRECTION_S
68340 if (qturns == 3) rotated_dir = OBC_DIRECTION_E
6835 case (OBC_DIRECTION_W)
68360 if (qturns == 0) rotated_dir = OBC_DIRECTION_W
68370 if (qturns == 1) rotated_dir = OBC_DIRECTION_S
68380 if (qturns == 2) rotated_dir = OBC_DIRECTION_E
68390 if (qturns == 3) rotated_dir = OBC_DIRECTION_N
6840 case (OBC_DIRECTION_S)
68410 if (qturns == 0) rotated_dir = OBC_DIRECTION_S
68420 if (qturns == 1) rotated_dir = OBC_DIRECTION_E
68430 if (qturns == 2) rotated_dir = OBC_DIRECTION_N
68440 if (qturns == 3) rotated_dir = OBC_DIRECTION_W
6845 case (OBC_DIRECTION_E)
68460 if (qturns == 0) rotated_dir = OBC_DIRECTION_E
68470 if (qturns == 1) rotated_dir = OBC_DIRECTION_N
68480 if (qturns == 2) rotated_dir = OBC_DIRECTION_W
68490 if (qturns == 3) rotated_dir = OBC_DIRECTION_S
6850 case (OBC_NONE)
68510 rotated_dir = OBC_NONE
6852 case default ! This should never happen.
68530 rotated_dir = direction
6854 end select
6855 endif
6856
68570end function rotate_OBC_segment_direction
6858
6859!> Return the that the field would have after being rotated by the given number of quarter turns
68600function rotated_field_name(input_name, turns)
6861 character(len=*), intent(in) :: input_name !< The unrotated field name
6862 integer, intent(in) :: turns !< Number of quarter turns of the grid
6863 character(len=len(input_name)) :: rotated_field_name !< The rotated field name
6864
68650 if (modulo(turns, 2) /= 0) then
68660 select case (input_name)
68670 case ('U') ; rotated_field_name = 'V'
68680 case ('Uamp') ; rotated_field_name = 'Vamp'
68690 case ('Uphase') ; rotated_field_name = 'Vphase'
68700 case ('V') ; rotated_field_name = 'U'
68710 case ('Vamp') ; rotated_field_name = 'Uamp'
68720 case ('Vphase') ; rotated_field_name = 'Uphase'
68730 case ('DVDX') ; rotated_field_name = 'DUDY'
68740 case ('DUDY') ; rotated_field_name = 'DVDX'
68750 case default ; rotated_field_name = input_name
6876 end select
6877 else
68780 rotated_field_name = input_name
6879 endif
6880
68810end function rotated_field_name
6882
6883!> Allocate an array of data for a field on a segment based on the size of a potentially rotated source array
68840subroutine allocate_rotated_seg_data(src_array, HI_in, tgt_array, segment)
6885 real, dimension(:,:,:), intent(in) :: src_array !< The segment data on the unrotated source grid
6886 type(hor_index_type), intent(in) :: HI_in !< Horizontal indices on the source grid
6887 real, dimension(:,:,:), allocatable, intent(inout) :: tgt_array !< The segment data that is being allocated
6888 type(OBC_segment_type), intent(inout) :: segment !< OBC segment on the target grid
6889
6890 ! Local variables
6891 integer :: isd, ied, jsd, jed, IsdB, IedB, JsdB, JedB, nk
6892 logical :: corner ! True if this field is discretized at the OBC segment nodes rather than the faces.
6893
68940 isd = segment%HI%isd ; ied = segment%HI%ied ; IsdB = segment%HI%IsdB ; IedB = segment%HI%IedB
68950 jsd = segment%HI%jsd ; jed = segment%HI%jed ; JsdB = segment%HI%JsdB ; JedB = segment%HI%JedB
68960 nk = size(src_array, 3)
6897
6898 ! Determine whether the source array is allocated at a segment face or at the corners.
6899 corner = (size(src_array, 1) == abs(HI_in%IedB - HI_in%IsdB) + 1 ) .and. &
69000 (size(src_array, 2) == abs(HI_in%JedB - HI_in%JsdB) + 1 )
6901
69020 if (corner) then
69030 allocate(tgt_array(IsdB:IedB,JsdB:JedB,nk), source=0.0)
69040 elseif (segment%is_E_or_W) then
69050 allocate(tgt_array(IsdB:IedB,jsd:jed,nk), source=0.0)
69060 elseif (segment%is_N_or_S) then
69070 allocate(tgt_array(isd:ied,JsdB:JedB,nk), source=0.0)
6908 endif
69090end subroutine allocate_rotated_seg_data
6910
6911
6912!> Write out information about the contents of the OBC control structure
69130subroutine write_OBC_info(OBC, G, GV, US)
6914 type(ocean_OBC_type), pointer :: OBC !< An open boundary condition control structure
6915 type(ocean_grid_type), intent(in) :: G !< Rotated grid metric
6916 type(verticalGrid_type), intent(in) :: GV !< Vertical grid
6917 type(unit_scale_type), intent(in) :: US !< Unit scaling
6918
6919 ! Local variables
6920 type(OBC_segment_type), pointer :: segment => NULL() ! pointer to segment type list
6921 integer :: turns ! Number of index quarter turns
6922 integer :: n ! The segment number reported in output
6923 integer :: n_seg ! The internal segment number
6924 integer :: dir ! This indicates the internal logical orientation of a segment
6925 integer :: unrot_dir ! This indicates the logical orientation a segment would have had
6926 ! without grid rotation
6927 integer :: c ! Used to loop over tidal constituents
6928 character(len=1024) :: mesg
6929
69300 turns = modulo(G%HI%turns, 4)
6931
69320 write(mesg, '("OBC has ", I0, " segments.")') OBC%number_of_segments
69330 call MOM_mesg(mesg, verb=1)
6934 ! call MOM_error(WARNING, mesg)
6935
69360 if (modulo(turns, 2) == 0) then
69370 if (OBC%open_u_BCs_exist_globally) call MOM_mesg("open_u_BCs_exist_globally", verb=1)
69380 if (OBC%open_v_BCs_exist_globally) call MOM_mesg("open_v_BCs_exist_globally", verb=1)
69390 if (OBC%Flather_u_BCs_exist_globally) call MOM_mesg("Flather_u_BCs_exist_globally", verb=1)
69400 if (OBC%Flather_v_BCs_exist_globally) call MOM_mesg("Flather_v_BCs_exist_globally", verb=1)
69410 if (OBC%nudged_u_BCs_exist_globally) call MOM_mesg("nudged_u_BCs_exist_globally", verb=1)
69420 if (OBC%nudged_v_BCs_exist_globally) call MOM_mesg("nudged_v_BCs_exist_globally", verb=1)
69430 if (OBC%specified_u_BCs_exist_globally) call MOM_mesg("specified_u_BCs_exist_globally", verb=1)
69440 if (OBC%specified_v_BCs_exist_globally) call MOM_mesg("specified_v_BCs_exist_globally", verb=1)
6945 else ! The u- and v-directions are swapped.
69460 if (OBC%open_v_BCs_exist_globally) call MOM_mesg("open_u_BCs_exist_globally", verb=1)
69470 if (OBC%open_u_BCs_exist_globally) call MOM_mesg("open_v_BCs_exist_globally", verb=1)
69480 if (OBC%Flather_v_BCs_exist_globally) call MOM_mesg("Flather_u_BCs_exist_globally", verb=1)
69490 if (OBC%Flather_u_BCs_exist_globally) call MOM_mesg("Flather_v_BCs_exist_globally", verb=1)
69500 if (OBC%nudged_v_BCs_exist_globally) call MOM_mesg("nudged_u_BCs_exist_globally", verb=1)
69510 if (OBC%nudged_u_BCs_exist_globally) call MOM_mesg("nudged_v_BCs_exist_globally", verb=1)
69520 if (OBC%specified_v_BCs_exist_globally) call MOM_mesg("specified_u_BCs_exist_globally", verb=1)
69530 if (OBC%specified_u_BCs_exist_globally) call MOM_mesg("specified_v_BCs_exist_globally", verb=1)
6954 endif
6955
69560 if (OBC%oblique_BCs_exist_globally) call MOM_mesg("oblique_BCs_exist_globally", verb=1)
69570 if (OBC%radiation_BCs_exist_globally) call MOM_mesg("radiation_BCs_exist_globally", verb=1)
69580 if (OBC%user_BCs_set_globally) call MOM_mesg("user_BCs_set_globally", verb=1)
69590 if (OBC%update_OBC) call MOM_mesg("update_OBC", verb=1)
69600 if (OBC%update_OBC_seg_data) call MOM_mesg("update_OBC_seg_data", verb=1)
69610 if (OBC%any_needs_IO_for_data) call MOM_mesg("any_needs_IO_for_data", verb=1)
69620 if (OBC%zero_biharmonic) call MOM_mesg("zero_biharmonic", verb=1)
69630 if (OBC%brushcutter_mode) call MOM_mesg("brushcutter_mode", verb=1)
69640 if (OBC%check_reconstruction) call MOM_mesg("check_reconstruction", verb=1)
69650 if (OBC%check_remapping) call MOM_mesg("check_remapping", verb=1)
69660 if (OBC%force_bounds_in_subcell) call MOM_mesg("force_bounds_in_subcell", verb=1)
69670 if (OBC%om4_remap_via_sub_cells) call MOM_mesg("om4_remap_via_sub_cells", verb=1)
69680 if (OBC%exterior_OBC_bug) call MOM_mesg("exterior_OBC_bug", verb=1)
69690 if (OBC%hor_index_bug) call MOM_mesg("hor_index_bug", verb=1)
69700 if (OBC%debug) call MOM_mesg("debug", verb=1)
69710 if (OBC%ramp) call MOM_mesg("ramp", verb=1)
69720 if (OBC%ramping_is_activated) call MOM_mesg("ramping_is_activated", verb=1)
69730 write(mesg, '("n_tide_constituents ", I0)') OBC%n_tide_constituents
69740 call MOM_mesg(mesg, verb=1)
69750 if (OBC%n_tide_constituents > 0) then
69760 do c=1,OBC%n_tide_constituents
6977 write(mesg, '(" properties ", 4ES16.6)') &
69780 US%s_to_T*OBC%tide_frequencies(c), OBC%tide_eq_phases(c), OBC%tide_fn(c), OBC%tide_un(c)
69790 call MOM_mesg(trim(OBC%tide_names(c))//mesg, verb=1)
6980 enddo
6981 endif
69820 if (OBC%ramp) then
69830 write(mesg, '("ramp_values ", 3ES16.6)') OBC%ramp_timescale, OBC%trunc_ramp_time, OBC%ramp_value
69840 call MOM_mesg(mesg, verb=1)
6985 endif
69860 write(mesg, '("gamma_uv ", ES16.6)') OBC%gamma_uv
69870 call MOM_mesg(mesg, verb=1)
69880 write(mesg, '("rx_max ", ES16.6)') OBC%rx_max
69890 call MOM_mesg(mesg, verb=1)
6990
69910 call MOM_mesg("remappingScheme = "//trim(OBC%remappingScheme), verb=1)
6992
69930 do n=1,OBC%number_of_segments
69940 n_seg = n ; if (OBC%reverse_segment_order) n_seg = OBC%number_of_segments + 1 - n
69950 segment => OBC%segment(n_seg)
69960 dir = segment%direction
6997
69980 unrot_dir = rotate_OBC_segment_direction(dir, -turns)
69990 write(mesg, '(" Segment ", I0, " has direction ", I0)') n, unrot_dir
70000 if (unrot_dir == OBC_DIRECTION_N) write(mesg, '(" Segment ", I0, " is Northern")') n
70010 if (unrot_dir == OBC_DIRECTION_S) write(mesg, '(" Segment ", I0, " is Southern")') n
70020 if (unrot_dir == OBC_DIRECTION_E) write(mesg, '(" Segment ", I0, " is Eastern")') n
70030 if (unrot_dir == OBC_DIRECTION_W) write(mesg, '(" Segment ", I0, " is Western")') n
70040 call MOM_mesg(mesg, verb=1)
7005
7006 ! write(mesg, '(" range:", 4(1x,I0))') segment%Is_obc, segment%Ie_obc, segment%Js_obc, segment%Je_obc
70070 if (modulo(turns, 2) == 0) then
70080 write(mesg, '(" size: ", I0," ",I0)') 1+abs(segment%Ie_obc-segment%Is_obc), 1+abs(segment%Je_obc-segment%Js_obc)
7009 else
70100 write(mesg, '(" size: ", I0," ",I0)') 1+abs(segment%Je_obc-segment%Js_obc), 1+abs(segment%Ie_obc-segment%Is_obc)
7011 endif
70120 call MOM_mesg(mesg, verb=1)
7013
70140 if (segment%on_pe) call MOM_mesg(" Segment is on PE.", verb=1)
7015
70160 if (segment%Flather) call MOM_mesg(" Flather", verb=1)
70170 if (segment%radiation) call MOM_mesg(" radiation", verb=1)
70180 if (segment%radiation_tan) call MOM_mesg(" radiation_tan", verb=1)
70190 if (segment%radiation_grad) call MOM_mesg(" radiation_grad", verb=1)
70200 if (segment%oblique) call MOM_mesg(" oblique", verb=1)
70210 if (segment%oblique_tan) call MOM_mesg(" oblique_tan", verb=1)
70220 if (segment%oblique_grad) call MOM_mesg(" oblique_grad", verb=1)
70230 if (segment%nudged) call MOM_mesg(" nudged", verb=1)
70240 if (segment%nudged_tan) call MOM_mesg(" nudged_tan", verb=1)
70250 if (segment%nudged_grad) call MOM_mesg(" nudged_grad", verb=1)
70260 if (segment%specified) call MOM_mesg(" specified", verb=1)
70270 if (segment%specified_tan) call MOM_mesg(" specified_tan", verb=1)
70280 if (segment%specified_grad) call MOM_mesg(" specified_grad", verb=1)
70290 if (segment%open) call MOM_mesg(" open", verb=1)
70300 if (segment%gradient) call MOM_mesg(" gradient", verb=1)
70310 if (modulo(turns, 2) == 0) then
70320 if (segment%is_N_or_S) call MOM_mesg(" is_N_or_S", verb=1)
70330 if (segment%is_E_or_W) call MOM_mesg(" is_E_or_W", verb=1)
7034 else ! The x- and y-directions are swapped.
70350 if (segment%is_E_or_W) call MOM_mesg(" is_N_or_S", verb=1)
70360 if (segment%is_N_or_S) call MOM_mesg(" is_E_or_W", verb=1)
7037 endif
7038! if (segment%is_E_or_W_2) call MOM_mesg(" is_E_or_W_2", verb=1)
70390 if (segment%temp_segment_data_exists) call MOM_mesg(" temp_segment_data_exists", verb=1)
70400 if (segment%salt_segment_data_exists) call MOM_mesg(" salt_segment_data_exists", verb=1)
7041
70420 write(mesg, '(" Tr_InvLscale_out ", ES16.6)') segment%Tr_InvLscale_out*US%m_to_L
70430 call MOM_mesg(mesg, verb=1)
70440 write(mesg, '(" Tr_InvLscale_in ", ES16.6)') segment%Tr_InvLscale_in*US%m_to_L
70450 call MOM_mesg(mesg, verb=1)
70460 write(mesg, '(" Th_InvLscale_out ", ES16.6)') segment%Th_InvLscale_out*US%m_to_L
70470 call MOM_mesg(mesg, verb=1)
70480 write(mesg, '(" Th_InvLscale_in ", ES16.6)') segment%Th_InvLscale_in*US%m_to_L
70490 call MOM_mesg(mesg, verb=1)
7050
7051 enddo
7052
70530 call chksum_OBC_segments(OBC, G, GV, US, 0)
7054
70550end subroutine write_OBC_info
7056
7057!> Write checksums and perhaps some or all of the values of all the allocated arrays on the OBC segments.
70580subroutine chksum_OBC_segments(OBC, G, GV, US, nk)
7059 type(ocean_OBC_type), intent(in) :: OBC !< An open boundary condition control structure
7060 type(ocean_grid_type), intent(in) :: G !< Rotated grid metric
7061 type(verticalGrid_type), intent(in) :: GV !< Vertical grid
7062 type(unit_scale_type), intent(in) :: US !< Unit scaling
7063 integer, intent(in) :: nk !< The number of layers to print
7064
7065 ! Local variables
7066 integer :: n ! The segment number reported in output
7067 integer :: n_seg ! The internal segment number
7068
70690 do n=1,OBC%number_of_segments
70700 n_seg = n ; if (OBC%reverse_segment_order) n_seg = OBC%number_of_segments + 1 - n
7071
70720 call chksum_OBC_segment_data(OBC%segment(n_seg), GV, US, nk, n)
7073 enddo
7074
70750end subroutine chksum_OBC_segments
7076
7077
7078!> Write checksums and perhaps some or all of the values of all the allocated arrays on a single OBC segment.
70790subroutine chksum_OBC_segment_data(segment, GV, US, nk, nseg_out)
7080 type(OBC_segment_type), intent(in) :: segment !< Segment type to checksum
7081 type(verticalGrid_type), intent(in) :: GV !< Vertical grid
7082 type(unit_scale_type), intent(in) :: US !< Unit scaling
7083 integer, intent(in) :: nk !< The number of layers to print
7084 integer, intent(in) :: nseg_out !< The segment number reported in output
7085
7086 ! Local variables
7087 real :: norm ! A sign change used when rotating a normal component [nondim]
7088 real :: tang ! A sign change used when rotating a tangential component [nondim]
7089 character(len=8) :: sn, segno
7090 integer :: dir ! This indicates the internal logical orientation of a segment
7091
70920 dir = segment%direction
7093
70940 write(segno, '(I0)') nseg_out
70950 sn = '('//trim(segno)//')'
7096
7097 ! Turn each segment and write it as though it is an eastern face.
70980 norm = 0.0 ; tang = 0.0
70990 if (dir == OBC_DIRECTION_E) then
71000 norm = 1.0 ; tang = 1.0
71010 elseif (dir == OBC_DIRECTION_N) then
71020 norm = 1.0 ; tang = -1.0
71030 elseif (dir == OBC_DIRECTION_W) then
71040 norm = -1.0 ; tang = -1.0
71050 elseif (dir == OBC_DIRECTION_S) then
71060 norm = -1.0 ; tang = 1.0
7107 endif
7108
71090 if (allocated(segment%Htot)) call write_2d_array_vals("Htot"//trim(sn), segment%Htot, dir, nk, unscale=GV%H_to_mks)
71100 if (allocated(segment%dZtot)) call write_2d_array_vals("dZtot"//trim(sn), segment%dZtot, dir, nk, unscale=US%Z_to_m)
71110 if (allocated(segment%SSH)) call write_2d_array_vals("SSH"//trim(sn), segment%SSH, dir, nk, unscale=US%Z_to_m)
71120 if (allocated(segment%normal_vel)) &
71130 call write_3d_array_vals("normal_vel"//trim(sn), segment%normal_vel, dir, nk, unscale=norm*US%L_T_to_m_s)
71140 if (allocated(segment%normal_vel_bt)) &
71150 call write_2d_array_vals("normal_vel_bt"//trim(sn), segment%normal_vel_bt, dir, nk, unscale=norm*US%L_T_to_m_s)
71160 if (allocated(segment%tangential_vel)) &
71170 call write_3d_array_vals("tangential_vel"//trim(sn), segment%tangential_vel, dir, nk, unscale=tang*US%L_T_to_m_s)
71180 if (allocated(segment%tangential_grad)) &
7119 call write_3d_array_vals("tangential_grad"//trim(sn), segment%tangential_grad, dir, nk, &
71200 unscale=tang*norm*US%s_to_T)
71210 if (allocated(segment%normal_trans)) &
7122 call write_3d_array_vals("normal_trans"//trim(sn), segment%normal_trans, dir, nk, &
71230 unscale=norm*GV%H_to_mks*US%L_T_to_m_s*US%L_to_m)
71240 if (allocated(segment%grad_normal)) &
71250 call write_3d_array_vals("grad_normal"//trim(sn), segment%grad_normal, dir, nk, unscale=norm*tang*US%L_T_to_m_s)
71260 if (allocated(segment%grad_tan)) &
71270 call write_3d_array_vals("grad_tan"//trim(sn), segment%grad_tan, dir, nk, unscale=1.0*US%L_T_to_m_s)
71280 if (allocated(segment%grad_gradient)) &
71290 call write_3d_array_vals("grad_gradient"//trim(sn), segment%grad_gradient, dir, nk, unscale=norm*US%s_to_T)
7130
71310 if (allocated(segment%rx_norm_rad)) &
71320 call write_3d_array_vals("rxy_norm_rad"//trim(sn), segment%rx_norm_rad, dir, nk, unscale=1.0)
71330 if (allocated(segment%ry_norm_rad)) &
71340 call write_3d_array_vals("rxy_norm_rad"//trim(sn), segment%ry_norm_rad, dir, nk, unscale=1.0)
71350 if (segment%is_E_or_W) then
71360 if (allocated(segment%rx_norm_obl)) &
71370 call write_3d_array_vals("rx_norm_obl"//trim(sn), segment%rx_norm_obl, dir, nk, unscale=US%L_T_to_m_s**2)
71380 if (allocated(segment%ry_norm_obl)) &
71390 call write_3d_array_vals("ry_norm_obl"//trim(sn), segment%ry_norm_obl, dir, nk, unscale=US%L_T_to_m_s**2)
7140 else ! The x- and y- directions are swapped.
71410 if (allocated(segment%ry_norm_obl)) &
71420 call write_3d_array_vals("rx_norm_obl"//trim(sn), segment%ry_norm_obl, dir, nk, unscale=US%L_T_to_m_s**2)
71430 if (allocated(segment%rx_norm_obl)) &
71440 call write_3d_array_vals("ry_norm_obl"//trim(sn), segment%rx_norm_obl, dir, nk, unscale=US%L_T_to_m_s**2)
7145 endif
7146
71470 if (allocated(segment%cff_normal)) &
71480 call write_3d_array_vals("cff_normal"//trim(sn), segment%cff_normal, dir, nk, unscale=US%L_T_to_m_s**2)
71490 if (allocated(segment%nudged_normal_vel)) &
7150 call write_3d_array_vals("nudged_normal_vel"//trim(sn), segment%nudged_normal_vel, dir, nk, &
71510 unscale=norm*US%L_T_to_m_s)
71520 if (allocated(segment%nudged_tangential_vel)) &
7153 call write_3d_array_vals("nudged_tangential_vel"//trim(sn), segment%nudged_tangential_vel, dir, nk, &
71540 unscale=tang*US%L_T_to_m_s)
71550 if (allocated(segment%nudged_tangential_grad)) &
7156 call write_3d_array_vals("nudged_tangential_grad"//trim(sn), segment%nudged_tangential_grad, dir, nk, &
71570 unscale=tang*norm*US%s_to_T)
7158
7159 contains
7160
7161 !> Write out the values in a named 2-d segment data array
71620 subroutine write_2d_array_vals(name, Array, seg_dir, nkp, unscale)
7163 character(len=*), intent(in) :: name !< The name of the variable
7164 real, dimension(:,:), intent(in) :: Array !< The 2-d array to write [A ~> a]
7165 integer, intent(in) :: seg_dir !< The direction of the segment
7166 integer, intent(in) :: nkp !< Print all the values if this is greater than 0
7167 real, optional, intent(in) :: unscale !< A factor that undoes the scaling of the array [a A-1 ~> 1]
7168 ! Local variables
7169 real :: scale ! A factor that undoes the scaling of the array [a A-1 ~> 1]
7170 character(len=1024) :: mesg
7171 character(len=24) :: val
7172 integer :: i, j, n, iounit
7173
71740 scale = 1.0 ; if (present(unscale)) scale = unscale
71750 iounit = stderr
7176
71770 if (nkp > 0) then
71780 write(iounit, '(2X,A,":")') trim(name)
71790 mesg = "" ; n = 0
71800 if ((seg_dir == OBC_DIRECTION_N) .or. (seg_dir == OBC_DIRECTION_W)) then
71810 do j=size(Array,2),1,-1 ; do i=size(Array,1),1,-1
71820 write(val, '(ES16.6)') scale*Array(i,j)
71830 mesg = trim(mesg)//" "//trim(val) ; n = n + 1
71840 if (n >= 12) then
71850 write(iounit, '(2X,A)') trim(mesg)
71860 mesg = "" ; n = 0
7187 endif
7188 enddo ; enddo
7189 else
71900 do j=1,size(Array,2) ; do i=1,size(Array,1)
71910 write(val, '(ES16.6)') scale*Array(i,j)
71920 mesg = trim(mesg)//" "//trim(val) ; n = n + 1
71930 if (n >= 12) then
71940 write(iounit, '(2X,A)') trim(mesg)
71950 mesg = "" ; n = 0
7196 endif
7197 enddo ; enddo
7198 endif
71990 if (n > 0) write(iounit, '(2X,A)') trim(mesg)
7200 endif
7201
72020 if (scale == 1.0) then
72030 call chksum(Array, name)
7204 else
72050 call chksum(scale*Array(:,:), name)
7206 endif
72070 end subroutine write_2d_array_vals
7208
7209 !> Write out the values in a 3-d segment data array
72100 subroutine write_3d_array_vals(name, Array, seg_dir, nkp, unscale)
7211 character(len=*), intent(in) :: name !< The name of the variable
7212 real, dimension(:,:,:), intent(in) :: Array !< The 3-d array to write
7213 integer, intent(in) :: seg_dir !< The direction of the segment
7214 integer, intent(in) :: nkp !< The number of layers to print
7215 real, optional, intent(in) :: unscale !< A factor that undoes the scaling of the array [a A-1 ~> 1]
7216 ! Local variables
7217 real :: scale ! A factor that undoes the scaling of the array [a A-1 ~> 1]
7218 logical :: reverse
7219 character(len=1024) :: mesg
7220 character(len=24) :: val
7221 integer :: i, j, k, n, nk, iounit
7222
72230 scale = 1.0 ; if (present(unscale)) scale = unscale
72240 iounit = stderr
7225
72260 if (nkp > 0) then
72270 nk = min(nkp, size(Array,3))
72280 write(iounit, '(2X,A,":")') trim(name)
72290 do k=1,nk
72300 mesg = "" ; n = 0
72310 if ((seg_dir == OBC_DIRECTION_N) .or. (seg_dir == OBC_DIRECTION_W)) then
72320 do j=size(Array,2),1,-1 ; do i=size(Array,1),1,-1
72330 write(val, '(ES16.6)') scale*Array(i,j,k)
72340 mesg = trim(mesg)//" "//trim(val) ; n = n + 1
72350 if (n >= 12) then
72360 write(iounit, '(2X,A)') trim(mesg)
72370 mesg = "" ; n = 0
7238 endif
7239 enddo ; enddo
7240 else
72410 do j=1,size(Array,2) ; do i=1,size(Array,1)
72420 write(val, '(ES16.6)') scale*Array(i,j,k)
72430 mesg = trim(mesg)//" "//trim(val) ; n = n + 1
72440 if (n >= 12) then
72450 write(iounit, '(2X,A)') trim(mesg)
72460 mesg = "" ; n = 0
7247 endif
7248 enddo ; enddo
7249 endif
72500 if (n > 0) write(iounit, '(2X,A)') trim(mesg)
7251 enddo
7252 endif
7253
72540 if (scale == 1.0) then
72550 call chksum(Array, name)
7256 else
72570 call chksum(scale*Array(:,:,:), name)
7258 endif
7259
72600 end subroutine write_3d_array_vals
7261
7262end subroutine chksum_OBC_segment_data
7263
7264!> \namespace mom_open_boundary
7265!! This module implements some aspects of internal open boundary
7266!! conditions in MOM.
7267!!
7268!! A small fragment of the grid is shown below:
7269!!
7270!! j+1 x ^ x ^ x At x: q, CoriolisBu
7271!! j+1 > o > o > At ^: v, tauy
7272!! j x ^ x ^ x At >: u, taux
7273!! j > o > o > At o: h, bathyT, buoy, tr, T, S, Rml, ustar
7274!! j-1 x ^ x ^ x
7275!! i-1 i i+1 At x & ^:
7276!! i i+1 At > & o:
7277!!
7278!! The boundaries always run through q grid points (x).
7279
72800end module MOM_open_boundary