Skip to content

Commit

Permalink
Merge pull request #122 from metno/bomb_decay
Browse files Browse the repository at this point in the history
Bomb decay
  • Loading branch information
heikoklein authored May 30, 2023
2 parents 0a27cc3 + d74b26a commit ce6c1ed
Show file tree
Hide file tree
Showing 22 changed files with 869 additions and 341 deletions.
16 changes: 11 additions & 5 deletions src/common/release.f90
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ subroutine release(istep,nsteph,tf1,tf2,tnow,ierror)
USE snapfldML, only: xm, ym, t1, t2, ps1, ps2
USE snapparML, only: time_profile, ncomp, nparnum, run_comp, &
iparnum, &
TIME_PROFILE_BOMB, TIME_PROFILE_STEPS
TIME_PROFILE_BOMB, TIME_PROFILE_LINEAR
USE snapposML, only: irelpos, release_positions
USE snaptabML, only: g, pmult, pitab
USE snapdimML, only: nx, ny, nk
Expand Down Expand Up @@ -158,9 +158,6 @@ subroutine release(istep,nsteph,tf1,tf2,tnow,ierror)

if(time_profile == TIME_PROFILE_BOMB) then
!..single bomb release
if (istep /= 0) then
return
endif
tstep=1.
else
if (istep >= releases(size(releases))%frelhour*nsteph) then
Expand All @@ -179,7 +176,8 @@ subroutine release(istep,nsteph,tf1,tf2,tnow,ierror)
! loop over all heights
do ih=1,nrelheight

if(time_profile /= TIME_PROFILE_STEPS .AND. nt < size(releases)) then
if(time_profile == TIME_PROFILE_LINEAR .and. nt < size(releases)) then
! linear release to next step
c1 = releases(nt)%frelhour*nsteph
c2 = releases(nt+1)%frelhour*nsteph
c3=istep
Expand All @@ -194,6 +192,7 @@ subroutine release(istep,nsteph,tf1,tf2,tnow,ierror)
! stemradius not with height profiles, nrelheight must be 1
stemradius = releases(nt)%relstemradius*rt1 + releases(nt+1)%relstemradius*rt2
else
! constant release rate between timesteps
do n=1,ncomp
relbq(n) = releases(nt)%relbqsec(n,ih)
end do
Expand All @@ -202,6 +201,13 @@ subroutine release(istep,nsteph,tf1,tf2,tnow,ierror)
hlower = releases(nt)%rellower(ih)
! stemradius not with height profiles, nrelheight must be 1
stemradius = releases(nt)%relstemradius
if (time_profile == TIME_PROFILE_BOMB) then
! complete release in one timestep
if(releases(nt)%frelhour*nsteph <= (istep-1)) then
! already released in previous timestep
return
end if
end if
end if

nprel=0
Expand Down
32 changes: 19 additions & 13 deletions src/common/snap.F90
Original file line number Diff line number Diff line change
Expand Up @@ -451,8 +451,6 @@ PROGRAM bsnap
!..total no. of timesteps to release particles
nstepr = nsteph*nhrel

!..nuclear bomb case
if (time_profile == TIME_PROFILE_BOMB) nstepr = 1

!..information to log file
write (iulog, *) 'nx,ny,nk: ', nx, ny, nk
Expand Down Expand Up @@ -895,7 +893,7 @@ subroutine first_nonblank(str, n)
!> reads information from an inputfile and loads into the program
subroutine read_inputfile(snapinput_unit)
use snapparML, only: push_down_dcomp, defined_component, &
TIME_PROFILE_CONSTANT, TIME_PROFILE_LINEAR, TIME_PROFILE_LINEAR, TIME_PROFILE_STEPS, &
TIME_PROFILE_CONSTANT, TIME_PROFILE_BOMB, TIME_PROFILE_LINEAR, TIME_PROFILE_STEPS, &
TIME_PROFILE_UNDEFINED
use snapfimexML, only: parse_interpolator
use snapgrdml, only: compute_column_max_conc, compute_aircraft_doserate, aircraft_doserate_threshold, &
Expand Down Expand Up @@ -1226,7 +1224,12 @@ subroutine read_inputfile(snapinput_unit)
elseif (ntprof > 1) then
if (keyword == 'release.bq/step.comp') then
do i = 1, ntprof - 1
rscale = 1./(3600.*(releases(i + 1)%frelhour - releases(i)%frelhour))
if (time_profile == TIME_PROFILE_BOMB) then
! everything released in one timestep
rscale = 1.
else
rscale = 1./(3600.*(releases(i + 1)%frelhour - releases(i)%frelhour))
end if
releases(i)%relbqsec(ncomp, 1) = releases(i)%relbqsec(ncomp, 1)*rscale
end do
end if
Expand Down Expand Up @@ -1777,7 +1780,7 @@ subroutine allocate_releases(string_with_commas, nelems)
!> checks the data from the input for errors or missing information,
!> and copies information to structures used when running the program.
subroutine conform_input(ierror)
use snapparML, only: TIME_PROFILE_UNDEFINED
use snapparML, only: TIME_PROFILE_UNDEFINED, TIME_PROFILE_BOMB
use snapfimexML, only: parse_interpolator
use snapgrdml, only: compute_aircraft_doserate
use find_parameter, only: detect_gridparams, get_klevel
Expand Down Expand Up @@ -1887,14 +1890,17 @@ subroutine conform_input(ierror)
ierror = 1
end if

do i = 1, ntprof - 1
if ((releases(i + 1)%frelhour - releases(i)%frelhour)*3600 < tstep) then
warning = .true.
write (error_unit, *) "WARNING: Release interval is shorter than timestep; ", &
"some releases may be skipped"
exit
endif
enddo
if (time_profile /= TIME_PROFILE_BOMB) then
! bomb releases everything at once, so the following check does not apply
do i = 1, ntprof - 1
if ((releases(i + 1)%frelhour - releases(i)%frelhour)*3600 < tstep) then
warning = .true.
write (error_unit, *) "WARNING: Release interval is shorter than timestep; ", &
"some releases may be skipped"
exit
endif
enddo
end if

if (ncomp < 0) then
write (error_unit, *) 'No (release) components specified for run'
Expand Down
2 changes: 2 additions & 0 deletions utils/SnapPy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ with status_number/text combinations like:
|202 | Finished extracting {model} data for ARGOS | Success | only code ARGOS reacts upon, all others are send to user |
|409 | {model} output data do not exist | Input Error | customer responsibility |
|409 | {model} output data does not exist, snap4rimsterm failed | Input Error | customers responsibility |
|409 | unknown input model {model} | Input Error | customer responsibility |
|410 | {model} internal error copying data to destination | Internal Error |
|410 | {model} internal error, zip failed | Internal Error |
|410 | {model} internal error, ncatted failed | Internal Error |
|500 | internal error, cannot start job in queue in dir '{rundir}'| Internal Error |
|500 | internal error in status tag | Internal Error |

status codes from status_exporter (alert-manager):
|code| explanation |
Expand Down
59 changes: 59 additions & 0 deletions utils/SnapPy/Snappy/AddBombIsotopes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#! /usr/bin/env python3

'''
Convert aerosols in a SNAP file to isotopes using the fractional distribution from Tovdal (2002)
'''

import netCDF4
from Snappy.BombIsotopeFractions import BombIsotopeFractions


def snap_add_bomb_isotopes(nc: netCDF4.Dataset):
'''
ncfile: a netcdf-file with Aerosols opened in 'a'-mode
'''
bomb_isotopes = BombIsotopeFractions()
aerosols = []
for var in nc.variables:
if var.startswith('Aerosol') and var.endswith('acc_concentration'):
aerosols.append(var[:-18])
isos = bomb_isotopes.isotopes()
hours = nc['time'][:] # snap writes usually hours since start
for var in ['concentration', 'acc_dry_deposition', 'acc_wet_deposition', 'acc_concentration']:
basevar = nc[f"{aerosols[0]}_{var}"]
for iso in isos:
# declare variables
name = f"{iso}_{var}"
if name not in nc.variables:
nc.createVariable(name, basevar.datatype, basevar.dimensions, zlib=True,
chunksizes=basevar.chunking())
for attr in basevar.ncattrs():
nc[name].setncattr(attr, basevar.getncattr(attr))
laststepdata = 0
for t, hr in enumerate(hours):
# convert data
basedata = 0
for aero in aerosols:
basedata += nc[f"{aero}_{var}"][t,:]
for iso in isos:
name = f"{iso}_{var}"
frac = bomb_isotopes.fraction(iso, hr)
if (var == 'acc_concentration') and t > 1:
# no decay in dose-equivalent
nc[name][t,:] = nc[name][t-1,:] + (basedata-laststepdata)*frac
else:
nc[name][t,:] = frac*basedata
laststepdata = basedata


def main():
import argparse
parser = argparse.ArgumentParser(description="add isotope distribution to a snap.nc with bomb-aerosols")
parser.add_argument("--nc", help="snap.nc filename", required=True)

args = parser.parse_args()
with netCDF4.Dataset(args.nc, 'a') as nc:
snap_add_bomb_isotopes(args.nc)

if __name__ == "__main__":
main()
76 changes: 76 additions & 0 deletions utils/SnapPy/Snappy/BombIsotopeFractions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import os
import csv
import re

class BombIsotopeFractions:
# fractions{isotope} = {0: frac0, 10: frac10, 20: frac20} with 0-40 in hours and frac0-frac40 fractions of total fission-products in Bq
_fractions = None
_timesteps = [0,10,20,30,40]
def __new__(cls):
'''BombIsotopeFractions singleton data'''
if cls._fractions is None:
cls._fractions = dict()
directory = os.path.join(os.path.dirname(__file__), "resources")
with open(
os.path.join(directory, "bomb-isotope-distribution_Tovedal.csv"), mode="r", encoding="UTF-8", newline=''
) as fh:
csvreader = csv.reader(fh, delimiter=',')
for i in range(2):
next(csvreader)
header = next(csvreader)
offset = 9
for i,hrs in enumerate(cls._timesteps):
if f"t={hrs}" not in header[offset+i]:
raise Exception(f"error in header for hour {hrs}: {header[offset+i]}")
for row in csvreader:
if '-' in row[0]:
isotope = row[0].replace('-', '') # without - as ususal in snappy
stepfraction = {}
for i,hrs in enumerate(cls._timesteps):
stepfraction[hrs] = float(row[offset+i])/100.
cls._fractions[isotope] = stepfraction
obj = object.__new__(cls)
return obj

def isotopes(self):
'''
list over isotopes as ['Cs137', 'Cs134', ...]
'''
return self._fractions.keys()

def fraction(self, isotope: str, hrs: int) -> float:
'''
@param isotope is a isotope name like Cs137 or Cs-137
@param hrs since bomb, intra/extrapolated
return a fraction of the total activity
'''
isotope = isotope.replace("-", "")
stepfracs = self._fractions[isotope]
if hrs < 0:
return 0
if hrs > self._timesteps[-1]:
return stepfracs[self._timesteps[-1]]
if hrs == self._timesteps[0]:
return stepfracs[self._timesteps[0]]

for i, nhr in enumerate(self._timesteps):
if nhr >= hrs:
phr = self._timesteps[i-1]
hfrac = (hrs-phr)/(nhr-phr)
nfrac = stepfracs[nhr]
pfrac = stepfracs[phr]
frac = pfrac + hfrac*(nfrac-pfrac)
return frac


if __name__ == "__main__":
bfracs = BombIsotopeFractions()
assert('Cs137' in bfracs.isotopes())
assert(bfracs.fraction('Cs137',0) == 0.0002/100)
assert(len(bfracs.isotopes()) > 10)
for hr in range(0,48):
tot = 0
for iso in bfracs.isotopes():
tot += bfracs.fraction(iso, hr)
assert(tot > .99)
assert(tot < 1.01)
50 changes: 46 additions & 4 deletions utils/SnapPy/Snappy/Resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,33 @@ def getIsotopes(self):
}
return isotopes

def isotopes2isoIds(self, isotopes: list[str | int]) -> list[int]:
'''
translate a list of isotopes, i.e. ['Cs-137', ...] or ['Cs137', ...] or ['17', ...]
to argos isotope id's
'''
retval = []
allIsos = self.getIsotopes()
for iso in isotopes:
isoId = -1
try:
iId = int(iso)
if iId in allIsos:
isoId = iId
except Exception:
pass
if isoId == -1:
iso = iso.replace('-', '')
for iId, isoDict in allIsos.items():
if iso == isoDict['isotope']:
isoId = iId
break
if isoId == -1:
raise Exception(f"no isotope-id for isotope {iso}")
retval.append(isoId)
return retval


def isotopes2snapinput(self, isotopeIds, add_DPUI=True):
"""Read a list of isotopeIds and return a text-block to be used for a snap.input file, like
COMPONENT= Cs137
Expand Down Expand Up @@ -253,7 +280,7 @@ def isotopes2snapinput(self, isotopeIds, add_DPUI=True):

return "\n".join(snapinputs)

def _getGribWriterConfig(self, isotopes, setFillValue=True):
def _getGribWriterConfig(self, isoIds, setFillValue=True):
"""Return a dictionary with a xml: xml-configuration string and a exracts: output-type and variable-names.
Use in conjunction with convert_to_grib.
"""
Expand All @@ -271,7 +298,7 @@ def _getGribWriterConfig(self, isotopes, setFillValue=True):
isoTemp = isoTemplate.read()

isoStr = ""
for isoId in isotopes:
for isoId in isoIds:
isoName = allIsos[isoId]["isotope"]
isoStr += isoTemp.format(ISOTOPE=isoName, ID=isoId)
extracts["conc"].append(f"{isoName}_concentration")
Expand Down Expand Up @@ -629,14 +656,23 @@ def getDoseCoefficients(self):
dosecoeffs = None
return dosecoeffs



# setting bitmapCompress as default to False
# fimex drops all fields which are completely missing, which argos doesn't like
# waiting for fimex-fix
def snapNc_convert_to_grib(snapNc, basedir, ident, isotopes, bitmapCompress=False):
"""simple function to implement conversion to grib snap.nc using fimex
and resources-setup
Parameters
----------
isotopes : list
list of isoIds [1,2,3,4...] or isotope-names like ['Cs-137', 'Cs-134',...]
"""
config = Resources()._getGribWriterConfig(isotopes, setFillValue=bitmapCompress)
res = Resources()
isoIds = res.isotopes2isoIds(isotopes)
config = res._getGribWriterConfig(isoIds, setFillValue=bitmapCompress)
xmlFile = "cdmGribWriterConfig.xml"
basexmlFile = os.path.join(basedir, xmlFile)
ncmlFile = "config.ncml"
Expand Down Expand Up @@ -717,4 +753,10 @@ def snapNc_convert_to_grib(snapNc, basedir, ident, isotopes, bitmapCompress=Fals
datetime.combine(date.today(), time(0)), -72
)
)
print(Resources().get_dosecoefficients())
print(Resources().getDoseCoefficients())
isotopes = ['Cs-137', 'Cs134']
isoIds = Resources().isotopes2isoIds(isotopes)
print(f"f{isotopes} have ids: {isoIds}")
assert len(isotopes) == len(isoIds)
# isotopes2isoIds idempotent
assert len(isoIds) == len(Resources().isotopes2isoIds(isoIds))
Loading

0 comments on commit ce6c1ed

Please sign in to comment.