MARBL_forcing_mod.F90

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!> This module provides a common datatype to provide forcing for MARBL tracers
6!! regardless of driver
8
9!! This module exists to house code used by multiple drivers in config_src/
10!! for passing forcing fields to MARBL
11!! (This comment can go in the wiki on the NCAR fork?)
12
13use mom_diag_mediator, only : safe_alloc_ptr, diag_ctrl
14use mom_time_manager, only : time_type
15use mom_error_handler, only : mom_error, warning, fatal
16use mom_file_parser, only : get_param, log_param, param_file_type
17use mom_grid, only : ocean_grid_type
19use mom_interpolate, only : external_field, init_external_field, time_interp_external
20use mom_io, only : slasher
21use marbl_constants_mod, only : molw_fe
22use mom_forcing_type, only : forcing
23
24implicit none ; private
25
26#include <MOM_memory.h>
27
28public :: marbl_forcing_init
30
31!> Control structure for this module
32type, public :: marbl_forcing_cs ; private
33 type(diag_ctrl), pointer :: diag => null() !< A structure that is used to
34 !! regulate the timing of diagnostic output.
35
36 real :: dust_ratio_thres !< coarse/fine dust ratio threshold [1]
37 real :: fe_bioavail_frac_offset !< offset for iron bioavailability fraction [1]
38 real :: atm_fe_to_bc_ratio !< atmospheric iron to black carbon ratio [1]
39 real :: seaice_fe_to_bc_ratio !< sea-ice iron to black carbon ratio [1]
40 real :: iron_frac_in_atm_fine_dust !< Fraction of fine dust from the atmosphere that is iron [1]
41 real :: iron_frac_in_atm_coarse_dust !< Fraction of coarse dust from the atmosphere that is iron [1]
42 real :: iron_frac_in_seaice_dust !< Fraction of dust from the sea ice that is iron [1]
43 real :: atm_co2_const !< atmospheric CO2 (if specifying a constant value) [ppm]
44 real :: atm_alt_co2_const !< alternate atmospheric CO2 for _ALT_CO2 tracers
45 !! (if specifying a constant value) [ppm]
46
47 logical :: use_marbl_tracers !< most functions can return immediately
48 !! MARBL tracers are turned off
49 integer :: atm_co2_iopt !< Integer version of atm_co2_opt, which determines source of atm_co2
50 integer :: atm_alt_co2_iopt !< Integer version of atm_alt_co2_opt, which determines source of atm_alt_co2
51
52end type marbl_forcing_cs
53
54! Module parameters
55integer, parameter :: atm_co2_constant_iopt = 0 !< module parameter denoting atm_co2_opt = 'constant'
56integer, parameter :: atm_co2_prognostic_iopt = 1 !< module parameter denoting atm_co2_opt = 'diagnostic'
57integer, parameter :: atm_co2_diagnostic_iopt = 2 !< module parameter denoting atm_co2_opt = 'prognostic'
58
59contains
60
61 subroutine marbl_forcing_init(G, US, param_file, diag, day, inputdir, use_MARBL_tracers, CS)
62 type(ocean_grid_type), intent(in) :: g !< The ocean's grid structure
63 type(unit_scale_type), intent(in) :: us !< A dimensional unit scaling type
64 type(param_file_type), intent(in) :: param_file !< A structure to parse for run-time parameters
65 type(diag_ctrl), target, intent(in) :: diag !< Structure used to regulate diagnostic output.
66 type(time_type), target, intent(in) :: day !< Time of the start of the run.
67 character(len=*), intent(in) :: inputdir !< Directory containing input files
68 logical, intent(in) :: use_marbl_tracers !< Is MARBL tracer package active?
69 type(marbl_forcing_cs), pointer, intent(inout) :: cs !< A pointer that is set to point to control
70 !! structure for MARBL forcing
71
72 character(len=40) :: mdl = "MARBL_forcing_mod" ! This module's name.
73 character(len=15) :: atm_co2_opt
74 character(len=200) :: err_message
75
76 if (associated(cs)) then
77 call mom_error(warning, "marbl_forcing_init called with an associated control structure.")
78 return
79 endif
80
81 allocate(cs)
82 cs%diag => diag
83
84 cs%use_MARBL_tracers = .true.
85 if (.not. use_marbl_tracers) then
86 cs%use_MARBL_tracers = .false.
87 return
88 endif
89
90 call get_param(param_file, mdl, "DUST_RATIO_THRES", cs%dust_ratio_thres, &
91 "coarse/fine dust ratio threshold", units="1", default=90.)
92 call get_param(param_file, mdl, "ATM_FE_TO_BC_RATIO", cs%atm_fe_to_bc_ratio, &
93 "atmospheric iron to black carbon ratio", units="1", default=1.33)
94 call get_param(param_file, mdl, "SEAICE_FE_TO_BC_RATIO", cs%seaice_fe_to_bc_ratio, &
95 "sea-ice iron to black carbon ratio", units="1", default=1.33)
96 call get_param(param_file, mdl, "IRON_FRAC_IN_ATM_FINE_DUST", cs%iron_frac_in_atm_fine_dust, &
97 "Fraction of fine dust from the atmosphere that is iron", units="1", default=0.035)
98 call get_param(param_file, mdl, "IRON_FRAC_IN_ATM_COARSE_DUST", cs%iron_frac_in_atm_coarse_dust, &
99 "Fraction of coarse dust from the atmosphere that is iron", units="1", default=0.035)
100 call get_param(param_file, mdl, "IRON_FRAC_IN_SEAICE_DUST", cs%iron_frac_in_seaice_dust, &
101 "Fraction of dust from sea ice that is iron", units="1", default=0.035)
102 call get_param(param_file, mdl, "ATM_CO2_OPT", atm_co2_opt, &
103 "Source of atmospheric CO2 [constant, diagnostic, or prognostic]", &
104 default="constant")
105 select case (trim(atm_co2_opt))
106 case("prognostic")
107 cs%atm_co2_iopt = atm_co2_prognostic_iopt
108 case("diagnostic")
109 cs%atm_co2_iopt = atm_co2_diagnostic_iopt
110 case("constant")
111 cs%atm_co2_iopt = atm_co2_constant_iopt
112 case DEFAULT
113 write(err_message, "(3A)") "'", trim(atm_co2_opt), "' is not a valid ATM_CO2_OPT value"
114 call mom_error(fatal, err_message)
115 end select
116 if (cs%atm_co2_iopt == atm_co2_constant_iopt) then
117 call get_param(param_file, mdl, "ATM_CO2_CONST", cs%atm_co2_const, &
118 "Value to send to MARBL as xco2", &
119 default=284.317, units="ppm")
120 endif
121 call get_param(param_file, mdl, "ATM_ALT_CO2_OPT", atm_co2_opt, &
122 "Source of alternate atmospheric CO2 [constant, diagnostic, or prognostic]", &
123 default="constant")
124 select case (trim(atm_co2_opt))
125 case("prognostic")
126 cs%atm_alt_co2_iopt = atm_co2_prognostic_iopt
127 case("diagnostic")
128 cs%atm_alt_co2_iopt = atm_co2_diagnostic_iopt
129 case("constant")
130 cs%atm_alt_co2_iopt = atm_co2_constant_iopt
131 case DEFAULT
132 write(err_message, "(3A)") "'", trim(atm_co2_opt), "' is not a valid ATM_ALT_CO2_OPT value"
133 call mom_error(fatal, err_message)
134 end select
135 if (cs%atm_alt_co2_iopt == atm_co2_constant_iopt) then
136 call get_param(param_file, mdl, "ATM_ALT_CO2_CONST", cs%atm_alt_co2_const, &
137 "Value to send to MARBL as xco2_alt_co2", &
138 default=284.317, units="ppm")
139 endif
140
141 end subroutine marbl_forcing_init
142
143 ! Note: ice fraction and u10_sqr are handled in mom_surface_forcing because of CFCs
144 subroutine convert_driver_fields_to_forcings(atm_fine_dust_flux, atm_coarse_dust_flux, &
145 seaice_dust_flux, atm_bc_flux, seaice_bc_flux, &
146 nhx_dep, noy_dep, atm_co2_prog, atm_co2_diag, &
147 afracr, swnet_afracr, ifrac_n, &
148 swpen_ifrac_n, Time, G, US, i0, j0, fluxes, CS)
149
150 real, dimension(:,:), pointer, intent(in) :: atm_fine_dust_flux !< atmosphere fine dust flux from IOB
151 !! [kg m-2 s-1]
152 real, dimension(:,:), pointer, intent(in) :: atm_coarse_dust_flux !< atmosphere coarse dust flux from IOB
153 !! [kg m-2 s-1]
154 real, dimension(:,:), pointer, intent(in) :: seaice_dust_flux !< sea ice dust flux from IOB [kg m-2 s-1]
155 real, dimension(:,:), pointer, intent(in) :: atm_bc_flux !< atmosphere black carbon flux from IOB
156 !! [kg m-2 s-1]
157 real, dimension(:,:), pointer, intent(in) :: seaice_bc_flux !< sea ice black carbon flux from IOB
158 !! [kg m-2 s-1]
159 real, dimension(:,:), pointer, intent(in) :: nhx_dep !< NHx flux from atmosphere [kg m-2 s-1]
160 real, dimension(:,:), pointer, intent(in) :: noy_dep !< NOy flux from atmosphere [kg m-2 s-1]
161 real, dimension(:,:), pointer, intent(in) :: atm_co2_prog !< Prognostic atmospheric CO2 concentration
162 !! [ppm]
163 real, dimension(:,:), pointer, intent(in) :: atm_co2_diag !< Diagnostic atmospheric CO2 concentration
164 !! [ppm]
165 real, dimension(:,:), pointer, intent(in) :: afracr !< open ocean fraction [1]
166 real, dimension(:,:), pointer, intent(in) :: swnet_afracr !< shortwave flux * open ocean fraction
167 !! [W m-2]
168 real, dimension(:,:,:), pointer, intent(in) :: ifrac_n !< per-category ice fraction [1]
169 real, dimension(:,:,:), pointer, intent(in) :: swpen_ifrac_n !< per-category shortwave flux * ice fraction
170 !! [W m-2]
171 type(time_type), intent(in) :: time !< The time of the fluxes, used for
172 !! interpolating the salinity to the
173 !! right time, when it is being
174 !! restored.
175 type(ocean_grid_type), intent(in) :: g !< The ocean's grid structure
176 type(unit_scale_type), intent(in) :: us !< A dimensional unit scaling type
177 integer, intent(in) :: i0 !< i index offset
178 integer, intent(in) :: j0 !< j index offset
179 type(forcing), intent(inout) :: fluxes !< MARBL-specific forcing fields
180 type(marbl_forcing_cs), pointer, intent(inout) :: cs !< A pointer that is set to point to
181 !! control structure for MARBL forcing
182
183 integer :: i, j, is, ie, js, je, m
184 real :: atm_fe_bioavail_frac !< Fraction of iron from the atmosphere available for biological uptake [1]
185 real :: dust_ratio !< Ratio of coarse to fine dust from the atmosphere [1]
186 real :: seaice_fe_bioavail_frac !< Fraction of iron from sea ice available for biological uptake [1]
187 ! Note: following two conversion factors are used to both convert from km m-2 s-1 -> mmol m-2 s-1
188 !! AND cast in MOM6's unique dimensional consistency scaling system [conc Z T-1]
189 real :: iron_flux_conversion !< Factor to convert iron flux from kg m-2 s-1 -> mmol m-3 (m s-1)
190 !! [s m2 kg-1 conc Z T-1 ~> mmol kg-1]
191 real :: ndep_conversion !< Factor to convert nitrogen deposition from kg m-2 s-1 -> mmol m-3 (m s-1)
192 !! [s m2 kg-1 conc Z T-1 ~> mmol kg-1]
193
194 if (.not. cs%use_MARBL_tracers) return
195
196 is = g%isc ; ie = g%iec ; js = g%jsc ; je = g%jec
197 ndep_conversion = (1.e6/14.) * (us%m_to_Z * us%T_to_s)
198 iron_flux_conversion = (1.e6 / molw_fe) * (us%m_to_Z * us%T_to_s)
199
200 do j=js,je ; do i=is,ie
201 ! Components of dust flux
202 fluxes%atm_fine_dust_flux(i,j) = (g%mask2dT(i,j) * us%kg_m2s_to_RZ_T) * atm_fine_dust_flux(i-i0,j-j0)
203 fluxes%atm_coarse_dust_flux(i,j) = (g%mask2dT(i,j) * us%kg_m2s_to_RZ_T) * atm_coarse_dust_flux(i-i0,j-j0)
204 fluxes%seaice_dust_flux(i,j) = (g%mask2dT(i,j) * us%kg_m2s_to_RZ_T) * seaice_dust_flux(i-i0,j-j0)
205
206 ! Components of black carbon flux
207 fluxes%atm_bc_flux(i,j) = (g%mask2dT(i,j) * us%kg_m2s_to_RZ_T) * atm_bc_flux(i-i0,j-j0)
208 fluxes%seaice_bc_flux(i,j) = (g%mask2dT(i,j) * us%kg_m2s_to_RZ_T) * seaice_bc_flux(i-i0,j-j0)
209
210 ! Nitrogen Deposition
211 fluxes%nhx_dep(i,j) = (g%mask2dT(i,j) * ndep_conversion) * nhx_dep(i-i0,j-j0)
212 fluxes%noy_dep(i,j) = (g%mask2dT(i,j) * ndep_conversion) * noy_dep(i-i0,j-j0)
213 enddo ; enddo
214
215 ! Atmospheric CO2
216 select case (cs%atm_co2_iopt)
217 case (atm_co2_prognostic_iopt)
218 if (associated(atm_co2_prog)) then
219 do j=js,je ; do i=is,ie
220 fluxes%atm_co2(i,j) = g%mask2dT(i,j) * atm_co2_prog(i-i0,j-j0)
221 enddo ; enddo
222 else
223 call mom_error(fatal, &
224 "ATM_CO2_OPT = 'prognostic' but atmosphere is not providing this field")
225 endif
226 case (atm_co2_diagnostic_iopt)
227 if (associated(atm_co2_diag)) then
228 do j=js,je ; do i=is,ie
229 fluxes%atm_co2(i,j) = g%mask2dT(i,j) * atm_co2_diag(i-i0,j-j0)
230 enddo ; enddo
231 else
232 call mom_error(fatal, &
233 "ATM_CO2_OPT = 'diagnostic' but atmosphere is not providing this field")
234 endif
235 case (atm_co2_constant_iopt)
236 do j=js,je ; do i=is,ie
237 fluxes%atm_co2(i,j) = g%mask2dT(i,j) * cs%atm_co2_const
238 enddo ; enddo
239 end select
240
241 ! Alternate Atmospheric CO2
242 select case (cs%atm_alt_co2_iopt)
243 case (atm_co2_prognostic_iopt)
244 if (associated(atm_co2_prog)) then
245 do j=js,je ; do i=is,ie
246 fluxes%atm_alt_co2(i,j) = g%mask2dT(i,j) * atm_co2_prog(i-i0,j-j0)
247 enddo ; enddo
248 else
249 call mom_error(fatal, &
250 "ATM_ALT_CO2_OPT = 'prognostic' but atmosphere is not providing this field")
251 endif
252 case (atm_co2_diagnostic_iopt)
253 if (associated(atm_co2_diag)) then
254 do j=js,je ; do i=is,ie
255 fluxes%atm_alt_co2(i,j) = g%mask2dT(i,j) * atm_co2_diag(i-i0,j-j0)
256 enddo ; enddo
257 else
258 call mom_error(fatal, &
259 "ATM_ALT_CO2_OPT = 'diagnostic' but atmosphere is not providing this field")
260 endif
261 case (atm_co2_constant_iopt)
262 do j=js,je ; do i=is,ie
263 fluxes%atm_alt_co2(i,j) = g%mask2dT(i,j) * cs%atm_co2_const
264 enddo ; enddo
265 end select
266
267 ! Dust flux
268 if (associated(atm_fine_dust_flux)) then
269 do j=js,je ; do i=is,ie
270 fluxes%dust_flux(i,j) = (us%kg_m2s_to_RZ_T * g%mask2dT(i,j)) * &
271 ((atm_fine_dust_flux(i-i0,j-j0) + atm_coarse_dust_flux(i-i0,j-j0)) + &
272 seaice_dust_flux(i-i0,j-j0))
273 enddo ; enddo
274 endif
275
276 if (associated(atm_bc_flux)) then
277 do j=js,je ; do i=is,ie
278 ! TODO: abort if atm_fine_dust_flux and atm_coarse_dust_flux are not associated?
279 ! Contribution of atmospheric dust to iron flux
280 atm_fe_bioavail_frac = 0.005
281 if ((atm_coarse_dust_flux(i-i0,j-j0) > 0.) .and. (atm_fine_dust_flux(i-i0,j-j0)) > 0.) then
282 dust_ratio = max(atm_coarse_dust_flux(i-i0,j-j0) / atm_fine_dust_flux(i-i0,j-j0), 9.903)
283 else
284 dust_ratio = 9.903
285 endif
286 dust_ratio = dust_ratio - 5.5
287 if (dust_ratio < cs%dust_ratio_thres) &
288 atm_fe_bioavail_frac = dust_ratio**(-0.9) - 0.0134
289
290 ! Contribution of atmospheric dust to iron flux
291 fluxes%iron_flux(i,j) = (atm_fe_bioavail_frac * &
292 (cs%iron_frac_in_atm_fine_dust * atm_fine_dust_flux(i-i0,j-j0) + &
293 cs%iron_frac_in_atm_coarse_dust * atm_coarse_dust_flux(i-i0,j-j0)))
294
295 ! Contribution of atmospheric black carbon to iron flux
296 fluxes%iron_flux(i,j) = fluxes%iron_flux(i,j) + (atm_bc_flux(i-i0,j-j0) * &
297 (atm_fe_bioavail_frac * cs%atm_fe_to_bc_ratio))
298
299 seaice_fe_bioavail_frac = atm_fe_bioavail_frac
300 ! Contribution of seaice dust to iron flux
301 fluxes%iron_flux(i,j) = fluxes%iron_flux(i,j) + (seaice_fe_bioavail_frac * &
302 (cs%iron_frac_in_seaice_dust * seaice_dust_flux(i-i0,j-j0)))
303
304 ! Contribution of seaice black carbon to iron flux
305 fluxes%iron_flux(i,j) = fluxes%iron_flux(i,j) + (seaice_bc_flux(i-i0,j-j0) * &
306 (seaice_fe_bioavail_frac * cs%seaice_fe_to_bc_ratio))
307
308 ! Unit conversion (kg m-2 s-1 -> conc Z T-1)
309 fluxes%iron_flux(i,j) = (g%mask2dT(i,j) * iron_flux_conversion) * fluxes%iron_flux(i,j)
310
311 enddo ; enddo
312 endif
313
314 ! Per ice-category forcings
315 ! If the cap receives per-category fields, memory should be allocated in fluxes
316 if (associated(ifrac_n)) then
317 do j=js,je ; do i=is,ie
318 fluxes%fracr_cat(i,j,1) = min(1., afracr(i-i0,j-j0))
319 fluxes%qsw_cat(i,j,1) = swnet_afracr(i-i0,j-j0)
320 do m=1,size(ifrac_n, 3)
321 fluxes%fracr_cat(i,j,m+1) = min(1., ifrac_n(i-i0,j-j0,m))
322 fluxes%qsw_cat(i,j,m+1) = swpen_ifrac_n(i-i0,j-j0,m)
323 enddo
324 where (fluxes%fracr_cat(i,j,:) > 0.)
325 fluxes%qsw_cat(i,j,:) = fluxes%qsw_cat(i,j,:) / fluxes%fracr_cat(i,j,:)
326 elsewhere
327 fluxes%fracr_cat(i,j,:) = 0.
328 fluxes%qsw_cat(i,j,:) = 0.
329 endwhere
330 fluxes%fracr_cat(i,j,:) = g%mask2dT(i,j) * fluxes%fracr_cat(i,j,:)
331 fluxes%qsw_cat(i,j,:) = (us%W_m2_to_QRZ_T * g%mask2dT(i,j)) * fluxes%qsw_cat(i,j,:)
332 enddo ; enddo
333 endif
334
336
337end module marbl_forcing_mod