Repository: NCAR/DART
Pull request #850: Freeboard fwd operator for radar, laser
Author: login: hkershaw-brown
Labels: name: release!
description: bundle with next release
name: cice
description: Sea Ice models
Created: 2025-03-31T16:28:18Z | Merged: 2025-05-06T20:48:12Z

## Description:
<!--- Describe your changes -->
Please provide a summary of the change and include any relevant motivation and context. 

Part of CICE improvements from #841

### Fixes issue
<!--- link to github issue(s) -->
Fixes #843

### Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update

### Documentation changes needed?
<!-- Put an `x` in all the boxes that apply: -->
- [x] My change requires a change to the documentation.
  - [ ] I have updated the documentation accordingly.

### Tests
Please describe any tests you ran to verify your changes.
None yet.

## Checklist for merging

- [ ] Updated changelog entry
- [ ] Documentation updated
- [ ] Update conf.py

## Checklist for release
- [ ] Merge into main
- [ ] Create release from the main branch with appropriate tag
- [ ] Delete feature-branch

## Testing Datasets

- [ ] Dataset needed for testing available upon request
- [ ] Dataset download instructions included
- [ ] No dataset needed


Linked issue #843: obs_def_cice_mod.f90  get_expected_agreg_freeboard  unused arguments
`subroutine get_expected_agreg_freeboard` has unused arguments: var_sic, var_siv, var_snv

https://github.com/NCAR/DART/blob/c5600dcf4e69d198d262a42cdf72b51510932ad0/observations/forward_operators/obs_def_cice_mod.f90#L180

The calling code is passing in QTY_SEAICE_CONCENTR, QTY_SEAICE_VOLUME, QTY_SEAICE_SNOWVOLUME

https://github.com/NCAR/DART/blob/c5600dcf4e69d198d262a42cdf72b51510932ad0/observations/forward_operators/obs_def_cice_mod.f90#L40-L42



Diff:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 3add2ffba6..71ab881696 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -22,6 +22,21 @@ individual files.
 
 The changes are now listed with the most recent at the top.
 
+**May 6 2025 :: Fwd-op for SAT_SEAICE radar and laser. Tag v11.11.0**
+
+- Forward Operator for SAT_SEAICE_RADAR|LASER_FREEBOARD. 
+
+  *contributed by Molly Wieringa*
+
+Bug-fix:
+
+  - real128 removed from developer_tests for nvhpc compiler.
+
+Documentation updates:
+
+  - CONAGUA converter documentation updated.
+  - Radar observations documentation updated. 
+
 **May 1 2025 :: Bug-fixes: TIEGCM, BNRH. Tag v11.10.9**
 
 - Fix VTEC extrapolation calculation in TIEGCM model_mod.
diff --git a/conf.py b/conf.py
index 18a5a46365..01724c9e5b 100644
--- a/conf.py
+++ b/conf.py
@@ -21,7 +21,7 @@
 author = 'Data Assimilation Research Section'
 
 # The full version, including alpha/beta/rc tags
-release = '11.10.9'
+release = '11.11.0'
 root_doc = 'index'
 
 # -- General configuration ---------------------------------------------------
diff --git a/observations/forward_operators/obs_def_cice_mod.f90 b/observations/forward_operators/obs_def_cice_mod.f90
index 9dbca0463b..045d5df958 100644
--- a/observations/forward_operators/obs_def_cice_mod.f90
+++ b/observations/forward_operators/obs_def_cice_mod.f90
@@ -17,7 +17,8 @@
 !SAT_SEAICE_FY,                   QTY_SEAICE_FY,                 COMMON_CODE
 !SAT_SEAICE_AGREG_FY,             QTY_SEAICE_AGREG_FY
 !SAT_SEAICE_AGREG_SURFACETEMP,    QTY_SEAICE_AGREG_SURFACETEMP
-!SAT_SEAICE_AGREG_FREEBOARD,      QTY_SEAICE_AGREG_FREEBOARD
+!SAT_SEAICE_RADAR_FREEBOARD,      QTY_SEAICE_AGREG_FREEBOARD
+!SAT_SEAICE_LASER_FREEBOARD,      QTY_SEAICE_AGREG_FREEBOARD
 !SAT_SEAICE_AGREG_CONCENTR,       QTY_SEAICE_AGREG_CONCENTR
 !SAT_SEAICE_AGREG_VOLUME,         QTY_SEAICE_AGREG_VOLUME
 !SAT_SEAICE_AGREG_SNOWVOLUME,     QTY_SEAICE_AGREG_SNOWVOLUME
@@ -36,10 +37,12 @@
 
 !-----------------------------------------------------------------------------
 ! BEGIN DART PREPROCESS GET_EXPECTED_OBS_FROM_DEF
-!   case(SAT_SEAICE_AGREG_FREEBOARD)
+!   case(SAT_SEAICE_RADAR_FREEBOARD)
 !      call get_expected_agreg_freeboard(state_handle, ens_size, location, &
-!               QTY_SEAICE_CONCENTR, QTY_SEAICE_VOLUME, QTY_SEAICE_SNOWVOLUME, &
-!               expected_obs, istatus)
+!               obs_kind_ind, expected_obs, istatus)
+!   case(SAT_SEAICE_LASER_FREEBOARD)
+!      call get_expected_agreg_freeboard(state_handle, ens_size, location, &
+!               obs_kind_ind, expected_obs, istatus)
 !   case(SAT_SEAICE_AGREG_THICKNESS)
 !      call get_expected_agreg_thickness(state_handle, ens_size, location, &
 !               QTY_SEAICE_VOLUME, expected_obs, istatus)
@@ -66,7 +69,8 @@
 
 !-----------------------------------------------------------------------------
 ! BEGIN DART PREPROCESS READ_OBS_DEF
-!    case(SAT_SEAICE_AGREG_FREEBOARD,   &
+!    case(SAT_SEAICE_LASER_FREEBOARD,   &
+!         SAT_SEAICE_RADAR_FREEBOARD,   &
 !         SAT_SEAICE_AGREG_THICKNESS,   &
 !         SAT_SEAICE_AGREG_SNOWDEPTH,   &
 !         SAT_SEAICE_AGREG_CONCENTR,    &
@@ -80,7 +84,8 @@
 
 !-----------------------------------------------------------------------------
 ! BEGIN DART PREPROCESS WRITE_OBS_DEF
-!    case(SAT_SEAICE_AGREG_FREEBOARD,   &
+!    case(SAT_SEAICE_LASER_FREEBOARD,   &
+!         SAT_SEAICE_RADAR_FREEBOARD,   &
 !         SAT_SEAICE_AGREG_THICKNESS,   &
 !         SAT_SEAICE_AGREG_SNOWDEPTH,   &
 !         SAT_SEAICE_AGREG_CONCENTR,    &
@@ -94,7 +99,8 @@
 
 !-----------------------------------------------------------------------------
 ! BEGIN DART PREPROCESS INTERACTIVE_OBS_DEF
-!    case(SAT_SEAICE_AGREG_FREEBOARD,   &
+!    case(SAT_SEAICE_LASER_FREEBOARD,   &
+!         SAT_SEAICE_RADAR_FREEBOARD,   &
 !         SAT_SEAICE_AGREG_THICKNESS,   &
 !         SAT_SEAICE_AGREG_SNOWDEPTH,   &
 !         SAT_SEAICE_AGREG_CONCENTR,    &
@@ -128,7 +134,9 @@ module obs_def_cice_mod
                              QTY_SEAICE_SNOWVOLUME,  &
                              QTY_SEAICE_FY,          &
                              QTY_SEAICE_SURFACETEMP, &
-                             QTY_SEAICE_CATEGORY
+                             QTY_SEAICE_CATEGORY,    &
+                             SAT_SEAICE_LASER_FREEBOARD, &
+                             SAT_SEAICE_RADAR_FREEBOARD
 
 use  ensemble_manager_mod, only : ensemble_type
 
@@ -142,10 +150,7 @@ module obs_def_cice_mod
           get_expected_agreg_over_grid
 
 ! version controlled file description for error handling, do not edit
-character(len=*), parameter :: source   = &
-   "$URL$"
-character(len=*), parameter :: revision = "$Revision$"
-character(len=*), parameter :: revdate  = "$Date$"
+character(len=*), parameter :: source   = "obs_def_cice_mod"
 
 logical, save :: module_initialized = .false.
 integer       :: Ncat = 0
@@ -166,7 +171,6 @@ subroutine initialize_module(state_handle, ens_size)
 integer,             intent(in)  :: ens_size
 
 if (module_initialized) return
-call register_module(source, revision, revdate)
 module_initialized = .true.
 
 Ncat = get_number_of_ice_categories(state_handle, ens_size)
@@ -178,13 +182,13 @@ end subroutine initialize_module
 !> 
 
 subroutine get_expected_agreg_freeboard(state_handle, ens_size, location,  &
-                                       var_sic, var_siv, var_snv, &
-                                       agreg_fb, istatus)
+                                        obs_type, &
+                                        agreg_fb, istatus)
 
 type(ensemble_type), intent(in)  :: state_handle
 integer,             intent(in)  :: ens_size
 type(location_type), intent(in)  :: location
-integer,             intent(in)  :: var_sic, var_siv, var_snv
+integer,             intent(in)  :: obs_type
 real(r8),            intent(out) :: agreg_fb(ens_size)
 integer,             intent(out) :: istatus(ens_size)
 
@@ -200,6 +204,7 @@ subroutine get_expected_agreg_freeboard(state_handle, ens_size, location,  &
 integer  :: istatus1(ens_size)
 integer  :: istatus2(ens_size)
 integer  :: istatus3(ens_size)
+real(r8) :: ratio
 
 type(location_type) :: location_fake
 
@@ -209,6 +214,15 @@ subroutine get_expected_agreg_freeboard(state_handle, ens_size, location,  &
 
 if (.not.module_initialized) call initialize_module(state_handle, ens_size)
 
+if (obs_type == SAT_SEAICE_LASER_FREEBOARD) then
+   ratio = snow_dens/water_dens - 1.0_r8
+elseif (obs_type == SAT_SEAICE_RADAR_FREEBOARD) then
+   ratio = snow_dens/water_dens
+else
+   istatus(:) = 1
+   return
+endif
+
 loc_array = get_location(location)
 llat = loc_array(1)
 llon = loc_array(2)
@@ -236,10 +250,10 @@ subroutine get_expected_agreg_freeboard(state_handle, ens_size, location,  &
 
    where(istatus1==0 .and. istatus2==0) &
      fb_volume = fb_volume + ice_volume*(1 - ice_dens/water_dens) - &
-                             snow_volume*snow_dens/water_dens
+                             snow_volume*ratio
 end do
 
-where(istatus3==0 .and. agreg_sic>1e-6) agreg_fb = fb_volume/agreg_sic
+where(istatus3==0 .and. agreg_sic>1e-6_r8) agreg_fb = fb_volume/agreg_sic
 where(istatus1/=0) istatus = istatus1
 where(istatus2/=0) istatus = istatus2
 where(istatus3/=0) istatus = istatus3
@@ -429,13 +443,13 @@ function get_number_of_ice_categories(state_handle, ens_size)
    write(string2,*)'If you have more than ',MAX_CATEGORIES,' ice categories'
    write(string3,*)'modify "MAX_CATEGORIES", recompile and try again.'
    call error_handler(E_ERR,'get_number_of_ice_categories',string1, &
-              source, revision, revdate, text2=string2, text3=string3)
+              source, text2=string2, text3=string3)
 endif
 
 if (get_number_of_ice_categories == 0) then
    write(string1,*)'Could not determine the number of ice categories.'
    call error_handler(E_ERR,'get_number_of_ice_categories',string1, &
-              source, revision, revdate)
+              source)
 endif
 
 end function get_number_of_ice_categories


Reviews:
user: login: mollymwieringa
body: I'd recommend removing `SAT_SEAICE_AGREG_FREEBOARD` as an option. Freeboard obs of sea ice are pretty distinctly one or the other, and the inclusion of the snow depth in the forward operator for laser altimeter measurements does have a significant impact on the analysis. The user should need to clearly understand which version of freeboard they are assimilating. 

Everything else looks good-- I like the use of `ratio` and the condensed aggregation function. 
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T15:34:45Z
user: login: hkershaw-brown
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-03T13:27:22Z
user: login: mjs2369
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T16:40:17Z
user: login: hkershaw-brown
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-01T16:48:03Z
user: login: mjs2369
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T18:20:28Z
user: login: hkershaw-brown
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-05T19:23:54Z
user: login: mjs2369
body: Looks ready to merge!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T19:53:49Z

Review comments:
diff_hunk: @@ -209,6 +217,12 @@ subroutine get_expected_agreg_freeboard(state_handle, ens_size, location,  &
 
 if (.not.module_initialized) call initialize_module(state_handle, ens_size)
 
+if (obs_type == SAT_SEAICE_LASER_FREEBOARD) then
+   ratio = snow_dens/water_dens - 1
path: observations/forward_operators/obs_def_cice_mod.f90
user: login: hkershaw-brown
body: ```suggestion
   ratio = snow_dens/water_dens - 1.0_r8
```
created_at: 2025-04-03T13:27:21Z
author_association: MEMBER
diff_hunk: @@ -128,7 +134,8 @@ module obs_def_cice_mod
                              QTY_SEAICE_SNOWVOLUME,  &
                              QTY_SEAICE_FY,          &
                              QTY_SEAICE_SURFACETEMP, &
-                             QTY_SEAICE_CATEGORY
+                             QTY_SEAICE_CATEGORY,    &
+                             SAT_SEAICE_LASER_FREEBOARD
path: observations/forward_operators/obs_def_cice_mod.f90
user: login: mjs2369
body: Why isn't SAT_SEAICE_RADAR_FREEBOARD included here?
created_at: 2025-04-23T18:37:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -128,7 +134,8 @@ module obs_def_cice_mod
                              QTY_SEAICE_SNOWVOLUME,  &
                              QTY_SEAICE_FY,          &
                              QTY_SEAICE_SURFACETEMP, &
-                             QTY_SEAICE_CATEGORY
+                             QTY_SEAICE_CATEGORY,    &
+                             SAT_SEAICE_LASER_FREEBOARD
path: observations/forward_operators/obs_def_cice_mod.f90
user: login: mjs2369
body: @hkershaw-brown 
created_at: 2025-04-28T17:43:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -128,7 +134,8 @@ module obs_def_cice_mod
                              QTY_SEAICE_SNOWVOLUME,  &
                              QTY_SEAICE_FY,          &
                              QTY_SEAICE_SURFACETEMP, &
-                             QTY_SEAICE_CATEGORY
+                             QTY_SEAICE_CATEGORY,    &
+                             SAT_SEAICE_LASER_FREEBOARD
path: observations/forward_operators/obs_def_cice_mod.f90
user: login: hkershaw-brown
body: > Why isn't SAT_SEAICE_RADAR_FREEBOARD included here?

The if statement at 220 is an either/or since the routine is called with LASER or RADAR.

so it checks if SAT_SEAICE_LASER_FREEBOARD, if not it does RADAR.  
Do you think it would be better (more readable & error proof) to have:

```
if (obs_type == SAT_SEAICE_LASER_FREEBOARD) then
   ratio = snow_dens/water_dens - 1.0_r8
else (obs_type == SAT_SEAICE_RADAR_FREEBOARD) then
   ratio = snow_dens/water_dens
else
    istatus = 1 ! fail
    return
endif
```

created_at: 2025-05-01T16:48:03Z
author_association: MEMBER
diff_hunk: @@ -128,7 +134,8 @@ module obs_def_cice_mod
                              QTY_SEAICE_SNOWVOLUME,  &
                              QTY_SEAICE_FY,          &
                              QTY_SEAICE_SURFACETEMP, &
-                             QTY_SEAICE_CATEGORY
+                             QTY_SEAICE_CATEGORY,    &
+                             SAT_SEAICE_LASER_FREEBOARD
path: observations/forward_operators/obs_def_cice_mod.f90
user: login: mjs2369
body: yep that makes sense

I think it would be better to have the code snippet above instead so if the obs_type is neither SAT_SEAICE_LASER_FREEBOARD or SAT_SEAICE_RADAR_FREEBOARD it errors out
created_at: 2025-05-01T18:20:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -128,7 +134,8 @@ module obs_def_cice_mod
                              QTY_SEAICE_SNOWVOLUME,  &
                              QTY_SEAICE_FY,          &
                              QTY_SEAICE_SURFACETEMP, &
-                             QTY_SEAICE_CATEGORY
+                             QTY_SEAICE_CATEGORY,    &
+                             SAT_SEAICE_LASER_FREEBOARD
path: observations/forward_operators/obs_def_cice_mod.f90
user: login: hkershaw-brown
body: done in [b79bd3d](https://github.com/NCAR/DART/pull/850/commits/b79bd3d0e80ccb18e2aec7ee2a535b96bc32e85f)
created_at: 2025-05-05T19:23:54Z
author_association: MEMBER

PR comments:
user: login: hkershaw-brown
created_at: 2025-03-31T16:31:00Z
body: Left in SAT_SEAICE_AGREG_FREEBOARD. Not sure if this should be
removed to force people to pick laser or radar.
author_association: MEMBER
user: login: mjs2369
created_at: 2025-05-01T18:22:01Z
body: Good time to remove svn stuff?

https://github.com/NCAR/DART/blob/768fc7fed41bdfdd89cc412e751fa02458d8b6bb/observations/forward_operators/obs_def_cice_mod.f90#L151-L155
author_association: CONTRIBUTOR
user: login: hkershaw-brown
created_at: 2025-05-05T19:56:47Z
body: > Good time to remove svn stuff?
> 
> https://github.com/NCAR/DART/blob/768fc7fed41bdfdd89cc412e751fa02458d8b6bb/observations/forward_operators/obs_def_cice_mod.f90#L151-L155

done in [235f520](https://github.com/NCAR/DART/pull/850/commits/235f52079e7b20730408420c71070fbcc6c65d26)
author_association: MEMBER