From d25870a676492cffdbaeb5f456363f8dfee468f7 Mon Sep 17 00:00:00 2001 From: "guoqing.ge" Date: Sat, 21 Mar 2026 19:30:17 -0600 Subject: [PATCH 01/11] improve parse_output_timelevel_spec using mpas_split_string --- src/framework/mpas_stream_manager.F | 75 +++++++++++++++-------------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/src/framework/mpas_stream_manager.F b/src/framework/mpas_stream_manager.F index 48e07d7fe7..9a017229cc 100644 --- a/src/framework/mpas_stream_manager.F +++ b/src/framework/mpas_stream_manager.F @@ -6230,60 +6230,58 @@ subroutine parse_output_timelevel_spec(spec, start_hour, end_hour, interval_minu real(kind=RKIND), intent(out) :: interval_minutes integer, intent(out) :: ierr - character(len=StrKIND) :: spec_local - integer :: i, dash_count, dash_pos(2), len_spec + character(len=StrKIND), pointer, dimension(:) :: parts + integer :: i, n_parts, local_ierr real(kind=RKIND) :: start_minutes, end_minutes, step_minutes - integer :: local_ierr ierr = 0 - dash_pos(1) = 0 - dash_pos(2) = 0 - spec_local = trim(adjustl(spec)) - len_spec = len_trim(spec_local) ! Guard against empty string - if (len_spec == 0) then + if (len_trim(spec) == 0) then ierr = 1 return end if - ! Count dashes to determine format - dash_count = 0 - do i = 1, len_spec - if (spec_local(i:i) == '-') then - dash_count = dash_count + 1 - if (dash_count <= 2) dash_pos(dash_count) = i + ! Split by dash delimiter + nullify(parts) + call mpas_split_string(trim(adjustl(spec)), '-', parts) + n_parts = size(parts) + + ! Validate parts array - check for empty parts (from consecutive dashes or leading/trailing dashes) + do i = 1, n_parts + if (len_trim(parts(i)) == 0) then + ierr = 1 + deallocate(parts) + return end if end do - ! Parse based on number of dashes - if (dash_count == 0) then + ! Parse based on number of parts + if (n_parts == 1) then ! Format: single time string (output at that time only) - call parse_time_string(spec_local, start_minutes, local_ierr) + call parse_time_string(parts(1), start_minutes, local_ierr) if (local_ierr /= 0) then ierr = 1 + deallocate(parts) return end if start_hour = start_minutes / 60.0_RKIND end_hour = start_hour interval_minutes = 60.0_RKIND ! Default, but won't matter for single time - else if (dash_count == 1) then + else if (n_parts == 2) then ! Format: start-stop (interval defaults to 1 hour) - if (dash_pos(1) <= 1 .or. dash_pos(1) >= len_spec) then - ierr = 1 - return - end if - - call parse_time_string(spec_local(1:dash_pos(1)-1), start_minutes, local_ierr) + call parse_time_string(parts(1), start_minutes, local_ierr) if (local_ierr /= 0) then ierr = 1 + deallocate(parts) return end if - call parse_time_string(spec_local(dash_pos(1)+1:len_spec), end_minutes, local_ierr) + call parse_time_string(parts(2), end_minutes, local_ierr) if (local_ierr /= 0) then ierr = 1 + deallocate(parts) return end if @@ -6291,28 +6289,26 @@ subroutine parse_output_timelevel_spec(spec, start_hour, end_hour, interval_minu end_hour = end_minutes / 60.0_RKIND interval_minutes = 60.0_RKIND - else if (dash_count == 2) then + else if (n_parts == 3) then ! Format: start-stop-step - if (dash_pos(1) <= 1 .or. dash_pos(2) <= dash_pos(1) + 1 .or. dash_pos(2) >= len_spec) then - ierr = 1 - return - end if - - call parse_time_string(spec_local(1:dash_pos(1)-1), start_minutes, local_ierr) + call parse_time_string(parts(1), start_minutes, local_ierr) if (local_ierr /= 0) then ierr = 1 + deallocate(parts) return end if - call parse_time_string(spec_local(dash_pos(1)+1:dash_pos(2)-1), end_minutes, local_ierr) + call parse_time_string(parts(2), end_minutes, local_ierr) if (local_ierr /= 0) then ierr = 1 + deallocate(parts) return end if - call parse_time_string(spec_local(dash_pos(2)+1:len_spec), step_minutes, local_ierr) + call parse_time_string(parts(3), step_minutes, local_ierr) if (local_ierr /= 0) then ierr = 1 + deallocate(parts) return end if @@ -6321,11 +6317,20 @@ subroutine parse_output_timelevel_spec(spec, start_hour, end_hour, interval_minu interval_minutes = step_minutes else - ! More than 2 dashes - invalid format + ! Invalid number of parts (0 or > 3 dashes) ierr = 1 + deallocate(parts) return end if + deallocate(parts) + + ! Check for setting mistake "start-step-stop". The correct one should be "start-stop-step" + if ((n_parts == 3) .and. (interval_minutes > end_hour * 60.0_RKIND + 1.0e-4_RKIND)) then + call mpas_log_write('ERROR: output_timelevels segment '''//trim(spec)//''': ' // & + 'expected format is start-stop-step, but appears to be start-step-stop', MPAS_LOG_ERR) + end if + end subroutine parse_output_timelevel_spec!}}} From ff93d69fc0abd5609e7d2d6f0d5079013d2d4daa Mon Sep 17 00:00:00 2001 From: "guoqing.ge" Date: Sat, 21 Mar 2026 22:51:07 -0600 Subject: [PATCH 02/11] fine tuning --- src/framework/mpas_stream_manager.F | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/framework/mpas_stream_manager.F b/src/framework/mpas_stream_manager.F index 9a017229cc..d5fcb6cd57 100644 --- a/src/framework/mpas_stream_manager.F +++ b/src/framework/mpas_stream_manager.F @@ -6064,9 +6064,9 @@ subroutine parse_all_timelevels(timelevels_str, timelevel_spec, stream_name, ier return end if - ! Split the string by spaces + ! Split the string by spaces (trim leading/trailing whitespaces first) nullify(segments) - call mpas_split_string(timelevels_str, ' ', segments) + call mpas_split_string(trim(adjustl(timelevels_str)), ' ', segments) n_segments = size(segments) ! Check for too many segments From 76b5d4f0c153de62c7cbcee4487a0af50bd81d82 Mon Sep 17 00:00:00 2001 From: "guoqing.ge" Date: Sat, 21 Mar 2026 22:56:47 -0600 Subject: [PATCH 03/11] debug --- src/framework/mpas_stream_manager.F | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/framework/mpas_stream_manager.F b/src/framework/mpas_stream_manager.F index d5fcb6cd57..95c88321f1 100644 --- a/src/framework/mpas_stream_manager.F +++ b/src/framework/mpas_stream_manager.F @@ -6069,6 +6069,13 @@ subroutine parse_all_timelevels(timelevels_str, timelevel_spec, stream_name, ier call mpas_split_string(trim(adjustl(timelevels_str)), ' ', segments) n_segments = size(segments) + ! Debug: show what segments were created + call mpas_log_write('DEBUG: output_timelevels split into $i segments', MPAS_LOG_WARN, intArgs=(/n_segments/)) + do i = 1, n_segments + call mpas_log_write(' Segment $i: '''//trim(segments(i))//''' (len=$i)', & + MPAS_LOG_WARN, intArgs=(/i, len_trim(segments(i))/)) + end do + ! Check for too many segments if (n_segments > MAX_TIMELEVEL_SEGMENTS) then call mpas_log_write('Stream '''//trim(stream_name)//''': too many timelevel segments (max $i)', & @@ -6090,6 +6097,7 @@ subroutine parse_all_timelevels(timelevels_str, timelevel_spec, stream_name, ier if (local_ierr /= 0) then call mpas_log_write('Stream '''//trim(stream_name)//''': Invalid timelevel format. ' // & 'Expected ''start-end-interval'' but got '''//trim(segment)//'''', MPAS_LOG_ERR) + call mpas_log_write(' DEBUG: total segments=$i, failed at segment $i', MPAS_LOG_ERR, intArgs=(/n_segments, i/)) deallocate(segments) ierr = 1 return @@ -6100,6 +6108,10 @@ subroutine parse_all_timelevels(timelevels_str, timelevel_spec, stream_name, ier timelevel_spec % start_hour(timelevel_spec % n_segments) = start_hour timelevel_spec % end_hour(timelevel_spec % n_segments) = end_hour timelevel_spec % interval_minutes(timelevel_spec % n_segments) = interval_mins + + ! Debug output + call mpas_log_write(' DEBUG: Parsed segment $i: '''//trim(segment)//''' -> start=$r h, end=$r h, interval=$r min', & + MPAS_LOG_WARN, intArgs=(/i/), realArgs=(/start_hour, end_hour, interval_mins/)) end do deallocate(segments) From e5f8cc94217a272e33266de6eb481d6822dab12e Mon Sep 17 00:00:00 2001 From: "guoqing.ge" Date: Sat, 21 Mar 2026 23:10:20 -0600 Subject: [PATCH 04/11] debug2 --- src/framework/mpas_stream_manager.F | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/framework/mpas_stream_manager.F b/src/framework/mpas_stream_manager.F index 95c88321f1..b1ec556bb8 100644 --- a/src/framework/mpas_stream_manager.F +++ b/src/framework/mpas_stream_manager.F @@ -6378,6 +6378,8 @@ subroutine get_output_interval_from_timelevels(timelevel_spec, forecast_hour, in is_single_time = .false. min_next_time = huge(1.0_RKIND) + call mpas_log_write('DEBUG get_output_interval: Called with forecast_hour=$r h', MPAS_LOG_WARN, realArgs=(/forecast_hour/)) + ! Guard against unparsed spec if (.not. timelevel_spec % is_parsed .or. timelevel_spec % n_segments == 0) then ierr = 1 @@ -6393,11 +6395,13 @@ subroutine get_output_interval_from_timelevels(timelevel_spec, forecast_hour, in ! Check if current forecast hour falls in this range if (forecast_hour >= start_hour .and. forecast_hour <= end_hour) then found = .true. + call mpas_log_write(' Matched segment $i: start=$r, end=$r', MPAS_LOG_WARN, intArgs=(/i/), realArgs=(/start_hour, end_hour/)) ! Check if this is a single time point (not a range) if (abs(start_hour - end_hour) < 1.0e-6_RKIND) then is_single_time = .true. else ! It's a range - use the segment's interval + call mpas_log_write(' -> Range mode: interval=$r min', MPAS_LOG_WARN, realArgs=(/seg_interval/)) interval_minutes = seg_interval return end if @@ -6426,15 +6430,18 @@ subroutine get_output_interval_from_timelevels(timelevel_spec, forecast_hour, in ! Calculate interval to next time if (min_next_time < huge(1.0_RKIND)) then interval_minutes = (min_next_time - forecast_hour) * 60.0_RKIND + call mpas_log_write(' -> Next time: $r h, interval=$r min', MPAS_LOG_WARN, realArgs=(/min_next_time, interval_minutes/)) else ! No more times after this - interval_minutes = 0 signals last output interval_minutes = 0.0_RKIND + call mpas_log_write(' -> No more times, interval=0', MPAS_LOG_WARN) end if return end if ! If no matching range found, signal that no more output is needed if (.not. found) then + call mpas_log_write(' -> No matching segment found, no more output', MPAS_LOG_WARN) interval_minutes = 0.0_RKIND ierr = MPAS_STREAM_MGR_ERROR ! Signal no more output end if From dbbff445fe8f52eaf0dc93aa8be8bf67f97829e9 Mon Sep 17 00:00:00 2001 From: "guoqing.ge" Date: Sun, 22 Mar 2026 02:34:54 -0600 Subject: [PATCH 05/11] debug more --- src/framework/mpas_stream_manager.F | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/framework/mpas_stream_manager.F b/src/framework/mpas_stream_manager.F index b1ec556bb8..22e83f461b 100644 --- a/src/framework/mpas_stream_manager.F +++ b/src/framework/mpas_stream_manager.F @@ -6561,16 +6561,27 @@ subroutine update_variable_output_alarm(manager, stream, alarm_name, ierr)!{{{ call get_output_interval_from_timelevels(stream % timelevel_spec, forecast_hour, interval_minutes, ierr_tmp) if (ierr_tmp /= MPAS_STREAM_MGR_NOERR .or. interval_minutes <= 0.0_RKIND) then ! Current hour not in any range - check for future range + call mpas_log_write('DEBUG update_variable_output_alarm: NO MATCH at forecast_hour=$r', & + MPAS_LOG_WARN, realArgs=(/forecast_hour/)) + call mpas_log_write('DEBUG update_variable_output_alarm: alarm_name='//trim(alarm_name), MPAS_LOG_WARN) call get_next_timelevel_start(stream % timelevel_spec, forecast_hour, next_start_hour, next_interval_minutes, ierr_tmp) call mpas_remove_clock_alarm(manager % streamClock, alarm_name, ierr=ierr_tmp) + call mpas_log_write('DEBUG update_variable_output_alarm: (no match) remove ierr=$i', & + MPAS_LOG_WARN, intArgs=(/ierr_tmp/)) if (next_start_hour > 0.0_RKIND) then + call mpas_log_write('DEBUG update_variable_output_alarm: scheduling next at $r h', & + MPAS_LOG_WARN, realArgs=(/next_start_hour/)) ! Schedule alarm for when the next range starts call mpas_set_timeInterval(time_diff, dt=next_start_hour * 3600.0_RKIND, ierr=ierr_tmp) alarmTime_local = start_time + time_diff call mpas_set_timeInterval(alarmInterval_local, dt=next_interval_minutes * 60.0_RKIND, ierr=ierr_tmp) call mpas_add_clock_alarm(manager % streamClock, alarm_name, alarmTime_local, & alarmTimeInterval=alarmInterval_local, ierr=ierr_tmp) + call mpas_log_write('DEBUG update_variable_output_alarm: (no match) add ierr=$i', & + MPAS_LOG_WARN, intArgs=(/ierr_tmp/)) + else + call mpas_log_write('DEBUG update_variable_output_alarm: no more outputs scheduled', MPAS_LOG_WARN) end if if (present(ierr)) ierr = MPAS_STREAM_MGR_NOERR return @@ -6580,8 +6591,14 @@ subroutine update_variable_output_alarm(manager, stream, alarm_name, ierr)!{{{ call mpas_set_timeInterval(alarmInterval_local, dt=interval_minutes * 60.0_RKIND, ierr=ierr_tmp) local_ierr = ior(local_ierr, ierr_tmp) + call mpas_log_write('DEBUG update_variable_output_alarm: alarm_name='//trim(alarm_name), MPAS_LOG_WARN) + call mpas_log_write('DEBUG update_variable_output_alarm: forecast_hour=$r, interval_min=$r', & + MPAS_LOG_WARN, realArgs=(/forecast_hour, interval_minutes/)) + ! Remove old alarm call mpas_remove_clock_alarm(manager % streamClock, alarm_name, ierr=ierr_tmp) + call mpas_log_write('DEBUG update_variable_output_alarm: mpas_remove_clock_alarm ierr=$i', & + MPAS_LOG_WARN, intArgs=(/ierr_tmp/)) local_ierr = ior(local_ierr, ierr_tmp) ! Add new alarm with updated interval @@ -6589,6 +6606,8 @@ subroutine update_variable_output_alarm(manager, stream, alarm_name, ierr)!{{{ alarmTime_local = current_time + alarmInterval_local call mpas_add_clock_alarm(manager % streamClock, alarm_name, alarmTime_local, & alarmTimeInterval=alarmInterval_local, ierr=ierr_tmp) + call mpas_log_write('DEBUG update_variable_output_alarm: mpas_add_clock_alarm ierr=$i', & + MPAS_LOG_WARN, intArgs=(/ierr_tmp/)) local_ierr = ior(local_ierr, ierr_tmp) if (present(ierr)) ierr = local_ierr From 7918651bd24a1b262f5dc3f09485845bcf980787 Mon Sep 17 00:00:00 2001 From: "guoqing.ge" Date: Sun, 22 Mar 2026 02:51:37 -0600 Subject: [PATCH 06/11] debug further --- src/framework/mpas_stream_manager.F | 9 +++++++-- src/framework/mpas_timekeeping.F | 29 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/framework/mpas_stream_manager.F b/src/framework/mpas_stream_manager.F index 22e83f461b..ed97958665 100644 --- a/src/framework/mpas_stream_manager.F +++ b/src/framework/mpas_stream_manager.F @@ -1643,6 +1643,7 @@ logical function MPAS_stream_mgr_ringing_alarms(manager, streamID, direction, ie end if do while (associated(alarm_cursor)) if (mpas_is_alarm_ringing(manager % streamClock, alarm_cursor % name, ierr=local_ierr)) then + call mpas_log_write('DEBUG ringing_alarms: ALARM RINGING: '//trim(alarm_cursor % name), MPAS_LOG_WARN) MPAS_stream_mgr_ringing_alarms = .true. return end if @@ -6592,8 +6593,10 @@ subroutine update_variable_output_alarm(manager, stream, alarm_name, ierr)!{{{ local_ierr = ior(local_ierr, ierr_tmp) call mpas_log_write('DEBUG update_variable_output_alarm: alarm_name='//trim(alarm_name), MPAS_LOG_WARN) - call mpas_log_write('DEBUG update_variable_output_alarm: forecast_hour=$r, interval_min=$r', & - MPAS_LOG_WARN, realArgs=(/forecast_hour, interval_minutes/)) + call mpas_log_write('DEBUG update_variable_output_alarm: forecast_hour=$r h', & + MPAS_LOG_WARN, realArgs=(/forecast_hour/)) + call mpas_log_write('DEBUG update_variable_output_alarm: interval_minutes=$r min (=$r sec)', & + MPAS_LOG_WARN, realArgs=(/interval_minutes, interval_minutes * 60.0_RKIND/)) ! Remove old alarm call mpas_remove_clock_alarm(manager % streamClock, alarm_name, ierr=ierr_tmp) @@ -6604,6 +6607,8 @@ subroutine update_variable_output_alarm(manager, stream, alarm_name, ierr)!{{{ ! Add new alarm with updated interval ! Set alarm time to current_time + interval so next ring is in the future alarmTime_local = current_time + alarmInterval_local + call mpas_log_write('DEBUG update_variable_output_alarm: next alarm in $r min from now', & + MPAS_LOG_WARN, realArgs=(/interval_minutes/)) call mpas_add_clock_alarm(manager % streamClock, alarm_name, alarmTime_local, & alarmTimeInterval=alarmInterval_local, ierr=ierr_tmp) call mpas_log_write('DEBUG update_variable_output_alarm: mpas_add_clock_alarm ierr=$i', & diff --git a/src/framework/mpas_timekeeping.F b/src/framework/mpas_timekeeping.F index 659d9bb4f4..ecdc039edd 100644 --- a/src/framework/mpas_timekeeping.F +++ b/src/framework/mpas_timekeeping.F @@ -812,6 +812,8 @@ end subroutine mpas_print_alarm logical function mpas_is_alarm_ringing(clock, alarmID, interval, ierr) + use mpas_log, only : mpas_log_write + implicit none type (MPAS_Clock_type), intent(in) :: clock @@ -820,14 +822,17 @@ logical function mpas_is_alarm_ringing(clock, alarmID, interval, ierr) integer, intent(out), optional :: ierr type (MPAS_Alarm_type), pointer :: alarmPtr + logical :: alarm_found if (present(ierr)) ierr = 0 mpas_is_alarm_ringing = .false. + alarm_found = .false. alarmPtr => clock % alarmListHead do while (associated(alarmPtr)) if (trim(alarmPtr % alarmID) == trim(alarmID)) then + alarm_found = .true. if (alarmPtr % isSet) then if (mpas_in_ringing_envelope(clock, alarmPtr, interval, ierr)) then mpas_is_alarm_ringing = .true. @@ -838,6 +843,11 @@ logical function mpas_is_alarm_ringing(clock, alarmID, interval, ierr) alarmPtr => alarmPtr % next end do + ! Debug: show if variable output alarm was not found + if (index(alarmID, '_output') > 0 .and. .not. alarm_found) then + call mpas_log_write('DEBUG mpas_is_alarm_ringing: ALARM NOT FOUND: '//trim(alarmID)) + end if + end function mpas_is_alarm_ringing @@ -874,6 +884,8 @@ end subroutine mpas_get_clock_ringing_alarms logical function mpas_in_ringing_envelope(clock, alarmPtr, interval, ierr) + use mpas_log, only : mpas_log_write + implicit none type (MPAS_Clock_type), intent(in) :: clock @@ -883,6 +895,9 @@ logical function mpas_in_ringing_envelope(clock, alarmPtr, interval, ierr) type (MPAS_Time_type) :: alarmNow type (MPAS_Time_type) :: alarmThreshold + type (MPAS_TimeInterval_type) :: now_interval, threshold_interval + integer (kind=I8KIND) :: now_seconds, threshold_seconds + integer :: tmp_err alarmNow = mpas_get_clock_time(clock, MPAS_NOW, ierr) alarmThreshold = alarmPtr % ringTime @@ -902,6 +917,20 @@ logical function mpas_in_ringing_envelope(clock, alarmPtr, interval, ierr) if (alarmThreshold <= alarmNow) then mpas_in_ringing_envelope = .true. end if + + ! Debug: show comparison for variable output alarms (containing "_output") + if (index(alarmPtr % alarmID, '_output') > 0) then + now_interval = alarmNow - mpas_get_clock_time(clock, MPAS_START_TIME, tmp_err) + threshold_interval = alarmThreshold - mpas_get_clock_time(clock, MPAS_START_TIME, tmp_err) + call mpas_get_timeInterval(now_interval, S_i8=now_seconds, ierr=tmp_err) + call mpas_get_timeInterval(threshold_interval, S_i8=threshold_seconds, ierr=tmp_err) + call mpas_log_write('DEBUG in_ringing_envelope: alarm='//trim(alarmPtr % alarmID)// & + ', isRecurring=$l, isSet=$l', & + logicArgs=(/alarmPtr % isRecurring, alarmPtr % isSet/)) + call mpas_log_write('DEBUG in_ringing_envelope: now=$i s, threshold=$i s, ringing=$l', & + intArgs=(/int(now_seconds), int(threshold_seconds)/), & + logicArgs=(/mpas_in_ringing_envelope/)) + end if else if (present(interval)) then From 2fac3572cf170dc787248fe2f0ee4165f75f09fc Mon Sep 17 00:00:00 2001 From: "guoqing.ge" Date: Sun, 22 Mar 2026 03:20:25 -0600 Subject: [PATCH 07/11] bug fix --- src/framework/mpas_stream_manager.F | 30 ++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/framework/mpas_stream_manager.F b/src/framework/mpas_stream_manager.F index ed97958665..5bf547a4ad 100644 --- a/src/framework/mpas_stream_manager.F +++ b/src/framework/mpas_stream_manager.F @@ -1510,9 +1510,14 @@ subroutine MPAS_stream_mgr_reset_alarms(manager, streamID, direction, ierr)!{{{ alarm_cursor => stream % alarmList_out % head do while (associated(alarm_cursor)) if (mpas_is_alarm_ringing(manager % streamClock, alarm_cursor % name, ierr=local_ierr)) then - call mpas_reset_clock_alarm(manager % streamClock, alarm_cursor % name, ierr=local_ierr) - ! Update variable output alarms with new interval based on forecast hour - call update_variable_output_alarm(manager, stream, alarm_cursor % name, ierr=local_ierr) + ! For variable output streams, update_variable_output_alarm handles everything + ! (removes old alarm, adds new one with correct timing) + ! For regular streams, just reset the alarm + if (stream % timelevel_spec % is_parsed) then + call update_variable_output_alarm(manager, stream, alarm_cursor % name, ierr=local_ierr) + else + call mpas_reset_clock_alarm(manager % streamClock, alarm_cursor % name, ierr=local_ierr) + end if end if alarm_cursor => alarm_cursor % next end do @@ -1541,13 +1546,24 @@ subroutine MPAS_stream_mgr_reset_alarms(manager, streamID, direction, ierr)!{{{ alarm_cursor => manager % alarms_out % head do while (associated(alarm_cursor)) if (mpas_is_alarm_ringing(manager % streamClock, alarm_cursor % name, ierr=local_ierr)) then - call mpas_reset_clock_alarm(manager % streamClock, alarm_cursor % name, ierr=local_ierr) - ! Update variable output alarms for each stream associated with this alarm + ! Check if ANY associated stream uses variable output + ! If so, update_variable_output_alarm handles removal/recreation + ! Otherwise, use standard reset + resetAlarms = .false. ! Reuse this flag to track if any variable stream handled it stream_cursor => alarm_cursor % streamList % head do while (associated(stream_cursor)) - call update_variable_output_alarm(manager, stream_cursor, alarm_cursor % name, ierr=local_ierr) + ! stream_cursor % xref points to the actual stream + if (stream_cursor % xref % timelevel_spec % is_parsed) then + call update_variable_output_alarm(manager, stream_cursor % xref, alarm_cursor % name, ierr=local_ierr) + resetAlarms = .true. ! A variable stream handled it + end if stream_cursor => stream_cursor % next end do + ! For non-variable streams, reset the alarm normally + ! But ONLY if no variable stream already handled it + if (.not. resetAlarms) then + call mpas_reset_clock_alarm(manager % streamClock, alarm_cursor % name, ierr=local_ierr) + end if end if alarm_cursor => alarm_cursor % next end do @@ -6573,7 +6589,7 @@ subroutine update_variable_output_alarm(manager, stream, alarm_name, ierr)!{{{ if (next_start_hour > 0.0_RKIND) then call mpas_log_write('DEBUG update_variable_output_alarm: scheduling next at $r h', & MPAS_LOG_WARN, realArgs=(/next_start_hour/)) - ! Schedule alarm for when the next range starts + ! Schedule recurring alarm for when the next range starts call mpas_set_timeInterval(time_diff, dt=next_start_hour * 3600.0_RKIND, ierr=ierr_tmp) alarmTime_local = start_time + time_diff call mpas_set_timeInterval(alarmInterval_local, dt=next_interval_minutes * 60.0_RKIND, ierr=ierr_tmp) From 53f86040c5b6ee2aafaccd74d11e11114efa5d11 Mon Sep 17 00:00:00 2001 From: "guoqing.ge" Date: Sun, 22 Mar 2026 04:19:24 -0600 Subject: [PATCH 08/11] avoid floatpoint drift --- src/framework/mpas_stream_manager.F | 42 ++++++++++++++++++----------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/framework/mpas_stream_manager.F b/src/framework/mpas_stream_manager.F index 5bf547a4ad..86e648b7e9 100644 --- a/src/framework/mpas_stream_manager.F +++ b/src/framework/mpas_stream_manager.F @@ -6375,7 +6375,7 @@ end subroutine parse_output_timelevel_spec!}}} !> appropriate output interval in minutes. ! !----------------------------------------------------------------------- - subroutine get_output_interval_from_timelevels(timelevel_spec, forecast_hour, interval_minutes, ierr)!{{{ + subroutine get_output_interval_from_timelevels(timelevel_spec, forecast_hour, interval_minutes, ierr, next_output_hour)!{{{ implicit none @@ -6383,17 +6383,19 @@ subroutine get_output_interval_from_timelevels(timelevel_spec, forecast_hour, in real(kind=RKIND), intent(in) :: forecast_hour real(kind=RKIND), intent(out) :: interval_minutes integer, intent(out) :: ierr + real(kind=RKIND), intent(out), optional :: next_output_hour integer :: i real(kind=RKIND) :: start_hour, end_hour, seg_interval - real(kind=RKIND) :: min_next_time + real(kind=RKIND) :: next_hour logical :: found, is_single_time ierr = 0 interval_minutes = 60.0_RKIND ! Default to 1 hour + if (present(next_output_hour)) next_output_hour = -1.0_RKIND found = .false. is_single_time = .false. - min_next_time = huge(1.0_RKIND) + next_hour = huge(1.0_RKIND) call mpas_log_write('DEBUG get_output_interval: Called with forecast_hour=$r h', MPAS_LOG_WARN, realArgs=(/forecast_hour/)) @@ -6433,24 +6435,25 @@ subroutine get_output_interval_from_timelevels(timelevel_spec, forecast_hour, in ! For single times, check if > current if (abs(start_hour - end_hour) < 1.0e-6_RKIND) then - if (start_hour > forecast_hour + 1.0e-6_RKIND .and. start_hour < min_next_time) then - min_next_time = start_hour + if (start_hour > forecast_hour + 1.0e-6_RKIND .and. start_hour < next_hour) then + next_hour = start_hour end if else ! For ranges, check if start is after current - if (start_hour > forecast_hour + 1.0e-6_RKIND .and. start_hour < min_next_time) then - min_next_time = start_hour + if (start_hour > forecast_hour + 1.0e-6_RKIND .and. start_hour < next_hour) then + next_hour = start_hour end if end if end do ! Calculate interval to next time - if (min_next_time < huge(1.0_RKIND)) then - interval_minutes = (min_next_time - forecast_hour) * 60.0_RKIND - call mpas_log_write(' -> Next time: $r h, interval=$r min', MPAS_LOG_WARN, realArgs=(/min_next_time, interval_minutes/)) + if (next_hour < huge(1.0_RKIND)) then + interval_minutes = (next_hour - forecast_hour) * 60.0_RKIND + if (present(next_output_hour)) next_output_hour = next_hour else ! No more times after this - interval_minutes = 0 signals last output interval_minutes = 0.0_RKIND + if (present(next_output_hour)) next_output_hour = -1.0_RKIND call mpas_log_write(' -> No more times, interval=0', MPAS_LOG_WARN) end if return @@ -6553,7 +6556,7 @@ subroutine update_variable_output_alarm(manager, stream, alarm_name, ierr)!{{{ type (MPAS_Time_type) :: start_time, current_time, alarmTime_local type (MPAS_TimeInterval_type) :: time_diff, alarmInterval_local - real (kind=RKIND) :: forecast_hour, next_start_hour + real (kind=RKIND) :: forecast_hour, next_start_hour, next_output_hour real (kind=RKIND) :: interval_minutes, next_interval_minutes integer :: local_ierr, ierr_tmp integer (kind=I8KIND) :: seconds_diff @@ -6575,7 +6578,8 @@ subroutine update_variable_output_alarm(manager, stream, alarm_name, ierr)!{{{ forecast_hour = real(seconds_diff, RKIND) / 3600.0_RKIND ! Get next interval using pre-parsed timelevel_spec - call get_output_interval_from_timelevels(stream % timelevel_spec, forecast_hour, interval_minutes, ierr_tmp) + ! Also get the absolute next output hour to avoid floating-point drift + call get_output_interval_from_timelevels(stream % timelevel_spec, forecast_hour, interval_minutes, ierr_tmp, next_output_hour) if (ierr_tmp /= MPAS_STREAM_MGR_NOERR .or. interval_minutes <= 0.0_RKIND) then ! Current hour not in any range - check for future range call mpas_log_write('DEBUG update_variable_output_alarm: NO MATCH at forecast_hour=$r', & @@ -6621,10 +6625,16 @@ subroutine update_variable_output_alarm(manager, stream, alarm_name, ierr)!{{{ local_ierr = ior(local_ierr, ierr_tmp) ! Add new alarm with updated interval - ! Set alarm time to current_time + interval so next ring is in the future - alarmTime_local = current_time + alarmInterval_local - call mpas_log_write('DEBUG update_variable_output_alarm: next alarm in $r min from now', & - MPAS_LOG_WARN, realArgs=(/interval_minutes/)) + ! Use ABSOLUTE time from start_time to avoid floating-point drift + ! This ensures discrete times like "0m 15m 1h3m 2 3" hit exactly + if (next_output_hour > 0.0_RKIND) then + ! Compute alarm time absolutely: start_time + next_output_hour + call mpas_set_timeInterval(time_diff, dt=next_output_hour * 3600.0_RKIND, ierr=ierr_tmp) + alarmTime_local = start_time + time_diff + else + ! Fallback to relative calculation (for ranges) + alarmTime_local = current_time + alarmInterval_local + end if call mpas_add_clock_alarm(manager % streamClock, alarm_name, alarmTime_local, & alarmTimeInterval=alarmInterval_local, ierr=ierr_tmp) call mpas_log_write('DEBUG update_variable_output_alarm: mpas_add_clock_alarm ierr=$i', & From 76ef804ad0e82b20dcad33e3f955254ffb991e95 Mon Sep 17 00:00:00 2001 From: "guoqing.ge" Date: Sun, 22 Mar 2026 09:24:19 -0600 Subject: [PATCH 09/11] remove all log outputs added for the debug purpose --- src/framework/mpas_stream_manager.F | 40 ----------------------------- src/framework/mpas_timekeeping.F | 26 ------------------- 2 files changed, 66 deletions(-) diff --git a/src/framework/mpas_stream_manager.F b/src/framework/mpas_stream_manager.F index 86e648b7e9..f93fae075d 100644 --- a/src/framework/mpas_stream_manager.F +++ b/src/framework/mpas_stream_manager.F @@ -1659,7 +1659,6 @@ logical function MPAS_stream_mgr_ringing_alarms(manager, streamID, direction, ie end if do while (associated(alarm_cursor)) if (mpas_is_alarm_ringing(manager % streamClock, alarm_cursor % name, ierr=local_ierr)) then - call mpas_log_write('DEBUG ringing_alarms: ALARM RINGING: '//trim(alarm_cursor % name), MPAS_LOG_WARN) MPAS_stream_mgr_ringing_alarms = .true. return end if @@ -6086,13 +6085,6 @@ subroutine parse_all_timelevels(timelevels_str, timelevel_spec, stream_name, ier call mpas_split_string(trim(adjustl(timelevels_str)), ' ', segments) n_segments = size(segments) - ! Debug: show what segments were created - call mpas_log_write('DEBUG: output_timelevels split into $i segments', MPAS_LOG_WARN, intArgs=(/n_segments/)) - do i = 1, n_segments - call mpas_log_write(' Segment $i: '''//trim(segments(i))//''' (len=$i)', & - MPAS_LOG_WARN, intArgs=(/i, len_trim(segments(i))/)) - end do - ! Check for too many segments if (n_segments > MAX_TIMELEVEL_SEGMENTS) then call mpas_log_write('Stream '''//trim(stream_name)//''': too many timelevel segments (max $i)', & @@ -6114,7 +6106,6 @@ subroutine parse_all_timelevels(timelevels_str, timelevel_spec, stream_name, ier if (local_ierr /= 0) then call mpas_log_write('Stream '''//trim(stream_name)//''': Invalid timelevel format. ' // & 'Expected ''start-end-interval'' but got '''//trim(segment)//'''', MPAS_LOG_ERR) - call mpas_log_write(' DEBUG: total segments=$i, failed at segment $i', MPAS_LOG_ERR, intArgs=(/n_segments, i/)) deallocate(segments) ierr = 1 return @@ -6125,10 +6116,6 @@ subroutine parse_all_timelevels(timelevels_str, timelevel_spec, stream_name, ier timelevel_spec % start_hour(timelevel_spec % n_segments) = start_hour timelevel_spec % end_hour(timelevel_spec % n_segments) = end_hour timelevel_spec % interval_minutes(timelevel_spec % n_segments) = interval_mins - - ! Debug output - call mpas_log_write(' DEBUG: Parsed segment $i: '''//trim(segment)//''' -> start=$r h, end=$r h, interval=$r min', & - MPAS_LOG_WARN, intArgs=(/i/), realArgs=(/start_hour, end_hour, interval_mins/)) end do deallocate(segments) @@ -6397,8 +6384,6 @@ subroutine get_output_interval_from_timelevels(timelevel_spec, forecast_hour, in is_single_time = .false. next_hour = huge(1.0_RKIND) - call mpas_log_write('DEBUG get_output_interval: Called with forecast_hour=$r h', MPAS_LOG_WARN, realArgs=(/forecast_hour/)) - ! Guard against unparsed spec if (.not. timelevel_spec % is_parsed .or. timelevel_spec % n_segments == 0) then ierr = 1 @@ -6414,13 +6399,11 @@ subroutine get_output_interval_from_timelevels(timelevel_spec, forecast_hour, in ! Check if current forecast hour falls in this range if (forecast_hour >= start_hour .and. forecast_hour <= end_hour) then found = .true. - call mpas_log_write(' Matched segment $i: start=$r, end=$r', MPAS_LOG_WARN, intArgs=(/i/), realArgs=(/start_hour, end_hour/)) ! Check if this is a single time point (not a range) if (abs(start_hour - end_hour) < 1.0e-6_RKIND) then is_single_time = .true. else ! It's a range - use the segment's interval - call mpas_log_write(' -> Range mode: interval=$r min', MPAS_LOG_WARN, realArgs=(/seg_interval/)) interval_minutes = seg_interval return end if @@ -6454,14 +6437,12 @@ subroutine get_output_interval_from_timelevels(timelevel_spec, forecast_hour, in ! No more times after this - interval_minutes = 0 signals last output interval_minutes = 0.0_RKIND if (present(next_output_hour)) next_output_hour = -1.0_RKIND - call mpas_log_write(' -> No more times, interval=0', MPAS_LOG_WARN) end if return end if ! If no matching range found, signal that no more output is needed if (.not. found) then - call mpas_log_write(' -> No matching segment found, no more output', MPAS_LOG_WARN) interval_minutes = 0.0_RKIND ierr = MPAS_STREAM_MGR_ERROR ! Signal no more output end if @@ -6582,27 +6563,16 @@ subroutine update_variable_output_alarm(manager, stream, alarm_name, ierr)!{{{ call get_output_interval_from_timelevels(stream % timelevel_spec, forecast_hour, interval_minutes, ierr_tmp, next_output_hour) if (ierr_tmp /= MPAS_STREAM_MGR_NOERR .or. interval_minutes <= 0.0_RKIND) then ! Current hour not in any range - check for future range - call mpas_log_write('DEBUG update_variable_output_alarm: NO MATCH at forecast_hour=$r', & - MPAS_LOG_WARN, realArgs=(/forecast_hour/)) - call mpas_log_write('DEBUG update_variable_output_alarm: alarm_name='//trim(alarm_name), MPAS_LOG_WARN) call get_next_timelevel_start(stream % timelevel_spec, forecast_hour, next_start_hour, next_interval_minutes, ierr_tmp) call mpas_remove_clock_alarm(manager % streamClock, alarm_name, ierr=ierr_tmp) - call mpas_log_write('DEBUG update_variable_output_alarm: (no match) remove ierr=$i', & - MPAS_LOG_WARN, intArgs=(/ierr_tmp/)) if (next_start_hour > 0.0_RKIND) then - call mpas_log_write('DEBUG update_variable_output_alarm: scheduling next at $r h', & - MPAS_LOG_WARN, realArgs=(/next_start_hour/)) ! Schedule recurring alarm for when the next range starts call mpas_set_timeInterval(time_diff, dt=next_start_hour * 3600.0_RKIND, ierr=ierr_tmp) alarmTime_local = start_time + time_diff call mpas_set_timeInterval(alarmInterval_local, dt=next_interval_minutes * 60.0_RKIND, ierr=ierr_tmp) call mpas_add_clock_alarm(manager % streamClock, alarm_name, alarmTime_local, & alarmTimeInterval=alarmInterval_local, ierr=ierr_tmp) - call mpas_log_write('DEBUG update_variable_output_alarm: (no match) add ierr=$i', & - MPAS_LOG_WARN, intArgs=(/ierr_tmp/)) - else - call mpas_log_write('DEBUG update_variable_output_alarm: no more outputs scheduled', MPAS_LOG_WARN) end if if (present(ierr)) ierr = MPAS_STREAM_MGR_NOERR return @@ -6612,16 +6582,8 @@ subroutine update_variable_output_alarm(manager, stream, alarm_name, ierr)!{{{ call mpas_set_timeInterval(alarmInterval_local, dt=interval_minutes * 60.0_RKIND, ierr=ierr_tmp) local_ierr = ior(local_ierr, ierr_tmp) - call mpas_log_write('DEBUG update_variable_output_alarm: alarm_name='//trim(alarm_name), MPAS_LOG_WARN) - call mpas_log_write('DEBUG update_variable_output_alarm: forecast_hour=$r h', & - MPAS_LOG_WARN, realArgs=(/forecast_hour/)) - call mpas_log_write('DEBUG update_variable_output_alarm: interval_minutes=$r min (=$r sec)', & - MPAS_LOG_WARN, realArgs=(/interval_minutes, interval_minutes * 60.0_RKIND/)) - ! Remove old alarm call mpas_remove_clock_alarm(manager % streamClock, alarm_name, ierr=ierr_tmp) - call mpas_log_write('DEBUG update_variable_output_alarm: mpas_remove_clock_alarm ierr=$i', & - MPAS_LOG_WARN, intArgs=(/ierr_tmp/)) local_ierr = ior(local_ierr, ierr_tmp) ! Add new alarm with updated interval @@ -6637,8 +6599,6 @@ subroutine update_variable_output_alarm(manager, stream, alarm_name, ierr)!{{{ end if call mpas_add_clock_alarm(manager % streamClock, alarm_name, alarmTime_local, & alarmTimeInterval=alarmInterval_local, ierr=ierr_tmp) - call mpas_log_write('DEBUG update_variable_output_alarm: mpas_add_clock_alarm ierr=$i', & - MPAS_LOG_WARN, intArgs=(/ierr_tmp/)) local_ierr = ior(local_ierr, ierr_tmp) if (present(ierr)) ierr = local_ierr diff --git a/src/framework/mpas_timekeeping.F b/src/framework/mpas_timekeeping.F index ecdc039edd..d8938a19e6 100644 --- a/src/framework/mpas_timekeeping.F +++ b/src/framework/mpas_timekeeping.F @@ -822,17 +822,14 @@ logical function mpas_is_alarm_ringing(clock, alarmID, interval, ierr) integer, intent(out), optional :: ierr type (MPAS_Alarm_type), pointer :: alarmPtr - logical :: alarm_found if (present(ierr)) ierr = 0 mpas_is_alarm_ringing = .false. - alarm_found = .false. alarmPtr => clock % alarmListHead do while (associated(alarmPtr)) if (trim(alarmPtr % alarmID) == trim(alarmID)) then - alarm_found = .true. if (alarmPtr % isSet) then if (mpas_in_ringing_envelope(clock, alarmPtr, interval, ierr)) then mpas_is_alarm_ringing = .true. @@ -843,11 +840,6 @@ logical function mpas_is_alarm_ringing(clock, alarmID, interval, ierr) alarmPtr => alarmPtr % next end do - ! Debug: show if variable output alarm was not found - if (index(alarmID, '_output') > 0 .and. .not. alarm_found) then - call mpas_log_write('DEBUG mpas_is_alarm_ringing: ALARM NOT FOUND: '//trim(alarmID)) - end if - end function mpas_is_alarm_ringing @@ -884,8 +876,6 @@ end subroutine mpas_get_clock_ringing_alarms logical function mpas_in_ringing_envelope(clock, alarmPtr, interval, ierr) - use mpas_log, only : mpas_log_write - implicit none type (MPAS_Clock_type), intent(in) :: clock @@ -895,8 +885,6 @@ logical function mpas_in_ringing_envelope(clock, alarmPtr, interval, ierr) type (MPAS_Time_type) :: alarmNow type (MPAS_Time_type) :: alarmThreshold - type (MPAS_TimeInterval_type) :: now_interval, threshold_interval - integer (kind=I8KIND) :: now_seconds, threshold_seconds integer :: tmp_err alarmNow = mpas_get_clock_time(clock, MPAS_NOW, ierr) @@ -917,20 +905,6 @@ logical function mpas_in_ringing_envelope(clock, alarmPtr, interval, ierr) if (alarmThreshold <= alarmNow) then mpas_in_ringing_envelope = .true. end if - - ! Debug: show comparison for variable output alarms (containing "_output") - if (index(alarmPtr % alarmID, '_output') > 0) then - now_interval = alarmNow - mpas_get_clock_time(clock, MPAS_START_TIME, tmp_err) - threshold_interval = alarmThreshold - mpas_get_clock_time(clock, MPAS_START_TIME, tmp_err) - call mpas_get_timeInterval(now_interval, S_i8=now_seconds, ierr=tmp_err) - call mpas_get_timeInterval(threshold_interval, S_i8=threshold_seconds, ierr=tmp_err) - call mpas_log_write('DEBUG in_ringing_envelope: alarm='//trim(alarmPtr % alarmID)// & - ', isRecurring=$l, isSet=$l', & - logicArgs=(/alarmPtr % isRecurring, alarmPtr % isSet/)) - call mpas_log_write('DEBUG in_ringing_envelope: now=$i s, threshold=$i s, ringing=$l', & - intArgs=(/int(now_seconds), int(threshold_seconds)/), & - logicArgs=(/mpas_in_ringing_envelope/)) - end if else if (present(interval)) then From 07d1acb70a2ffb86fbbd1f8218482a7b914fc208 Mon Sep 17 00:00:00 2001 From: "guoqing.ge" Date: Sun, 22 Mar 2026 09:31:08 -0600 Subject: [PATCH 10/11] write timstamps in done marker files --- src/framework/mpas_stream_manager.F | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/framework/mpas_stream_manager.F b/src/framework/mpas_stream_manager.F index f93fae075d..2df6ab409b 100644 --- a/src/framework/mpas_stream_manager.F +++ b/src/framework/mpas_stream_manager.F @@ -7205,6 +7205,8 @@ subroutine write_done_marker(filename, dminfo, blockWrite)!{{{ logical, intent(in) :: blockWrite character(len=1024) :: marker_filename + character(len=8) :: date_str + character(len=10) :: time_str integer :: unit_num ! For blockWrite mode, each rank writes its own file so each creates marker @@ -7212,7 +7214,11 @@ subroutine write_done_marker(filename, dminfo, blockWrite)!{{{ if (blockWrite .or. dminfo % my_proc_id == IO_NODE) then marker_filename = trim(filename) // '.done' unit_num = 99 + call date_and_time(date=date_str, time=time_str) open(unit=unit_num, file=trim(marker_filename), status='replace', action='write') + ! Write timestamp: YYYY-MM-DD HH:MM:SS + write(unit_num, '(A)') date_str(1:4)//'-'//date_str(5:6)//'-'//date_str(7:8)//' '// & + time_str(1:2)//':'//time_str(3:4)//':'//time_str(5:6) close(unit_num) end if From 0ba621fbc9946fd4ce9ca1b4fc9109dbec4830ad Mon Sep 17 00:00:00 2001 From: "guoqing.ge" Date: Sun, 22 Mar 2026 10:17:47 -0600 Subject: [PATCH 11/11] remove artifcats --- src/framework/mpas_timekeeping.F | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/framework/mpas_timekeeping.F b/src/framework/mpas_timekeeping.F index d8938a19e6..659d9bb4f4 100644 --- a/src/framework/mpas_timekeeping.F +++ b/src/framework/mpas_timekeeping.F @@ -812,8 +812,6 @@ end subroutine mpas_print_alarm logical function mpas_is_alarm_ringing(clock, alarmID, interval, ierr) - use mpas_log, only : mpas_log_write - implicit none type (MPAS_Clock_type), intent(in) :: clock @@ -885,7 +883,6 @@ logical function mpas_in_ringing_envelope(clock, alarmPtr, interval, ierr) type (MPAS_Time_type) :: alarmNow type (MPAS_Time_type) :: alarmThreshold - integer :: tmp_err alarmNow = mpas_get_clock_time(clock, MPAS_NOW, ierr) alarmThreshold = alarmPtr % ringTime