From 2da40a3861d9e5052116e331dfaf3d9509d1372b Mon Sep 17 00:00:00 2001
From: James Brown <64858662+james-d-brown@users.noreply.github.com>
Date: Tue, 26 Nov 2024 14:41:53 +0000
Subject: [PATCH] Adjust the earliest valid time to account for the timescale
period when conducting an evaluation in memory, #369
---
.../wres/datamodel/time/TimeSeriesSlicer.java | 93 +++++++++++-------
.../datamodel/time/TimeSeriesSlicerTest.java | 28 +++++-
.../io/retrieving/RetrieverUtilities.java | 4 +-
.../database/TimeSeriesRetriever.java | 96 ++-----------------
.../EnsembleRetrieverFactoryInMemory.java | 8 +-
...eSingleValuedRetrieverFactoryInMemory.java | 8 +-
.../SingleValuedRetrieverFactoryInMemory.java | 8 +-
7 files changed, 107 insertions(+), 138 deletions(-)
diff --git a/wres-datamodel/src/wres/datamodel/time/TimeSeriesSlicer.java b/wres-datamodel/src/wres/datamodel/time/TimeSeriesSlicer.java
index 8ae01b4bc2..be759a9b63 100644
--- a/wres-datamodel/src/wres/datamodel/time/TimeSeriesSlicer.java
+++ b/wres-datamodel/src/wres/datamodel/time/TimeSeriesSlicer.java
@@ -1616,58 +1616,81 @@ public static TimeSeries snip( TimeSeries toSnip,
}
/**
- * Adjusts the earliest lead duration of the time window to account for the period associated with the desired time
- * scale in order to capture sufficient data for rescaling. If the time scale is instantaneous, no adjustment is
- * made.
+ * Subtracts any non-instantaneous desired timescale from the earliest lead duration and the earliest valid time to
+ * ensure that sufficient data is retrieved for upscaling.
*
- * @param timeWindow the time window to adjust, required
- * @param desiredTimeScale the desired time scale, lenient if null (returns the input time window)
+ * @param timeWindow the time window to adjust
+ * @param timeScale the timescale to use
* @return the adjusted time window
*/
- public static TimeWindowOuter adjustByTimeScalePeriod( TimeWindowOuter timeWindow, TimeScaleOuter desiredTimeScale )
+ public static TimeWindowOuter adjustTimeWindowForTimeScale( TimeWindowOuter timeWindow,
+ TimeScaleOuter timeScale )
{
- Objects.requireNonNull( timeWindow );
+ TimeWindow.Builder adjusted = timeWindow.getTimeWindow()
+ .toBuilder();
- if ( Objects.nonNull( desiredTimeScale ) && desiredTimeScale.isInstantaneous() )
+ // Earliest lead duration
+ if ( !timeWindow.getEarliestLeadDuration()
+ .equals( TimeWindowOuter.DURATION_MIN ) )
{
- LOGGER.debug( "Not adjusting the time window of {} with the time scale because the time scale is "
- + "instantaneous.", timeWindow );
- return timeWindow;
+ Duration period = Duration.ZERO;
+
+ // Adjust the lower bound of the lead duration window by the non-instantaneous desired timescale
+ if ( Objects.nonNull( timeScale )
+ && !timeScale.isInstantaneous() )
+ {
+ period = TimeScaleOuter.getOrInferPeriodFromTimeScale( timeScale );
+ }
+
+ Duration lowered = timeWindow.getEarliestLeadDuration()
+ .minus( period );
+
+ if ( Objects.nonNull( timeScale )
+ && LOGGER.isDebugEnabled() )
+ {
+ LOGGER.debug( "Adjusting the lower lead duration of time window {} from {} to {} "
+ + "in order to acquire data at the desired timescale of {}.",
+ timeWindow,
+ timeWindow.getEarliestLeadDuration(),
+ lowered,
+ timeScale );
+ }
+
+ adjusted.setEarliestLeadDuration( MessageFactory.getDuration( lowered ) );
}
- TimeWindowOuter adjustedWindow = timeWindow;
- if ( !timeWindow.getEarliestLeadDuration().equals( TimeWindowOuter.DURATION_MIN )
- && Objects.nonNull( desiredTimeScale ) )
+ // Earliest valid time
+ if ( !timeWindow.getEarliestValidTime()
+ .equals( Instant.MIN ) )
{
- Duration period = TimeScaleOuter.getOrInferPeriodFromTimeScale( desiredTimeScale );
- Duration lowerD = timeWindow.getEarliestLeadDuration()
- .minus( period );
- com.google.protobuf.Duration lower =
- com.google.protobuf.Duration.newBuilder()
- .setSeconds( lowerD.getSeconds() )
- .setNanos( lowerD.getNano() )
- .build();
+ Duration period = Duration.ZERO;
- TimeWindow inner = timeWindow.getTimeWindow()
- .toBuilder()
- .setEarliestLeadDuration( lower )
- .build();
+ // Adjust the lower bound of the lead duration window by the non-instantaneous desired timescale
+ if ( Objects.nonNull( timeScale )
+ && !timeScale.isInstantaneous() )
+ {
+ period = TimeScaleOuter.getOrInferPeriodFromTimeScale( timeScale );
+ }
- adjustedWindow = TimeWindowOuter.of( inner );
+ Instant lowered = timeWindow.getEarliestValidTime()
+ .minus( period );
- if ( LOGGER.isDebugEnabled() )
+ if ( Objects.nonNull( timeScale )
+ && LOGGER.isDebugEnabled() )
{
- LOGGER.debug(
- "Adjusted the earliest lead duration of {} by {} to {}, in order to select sufficient data "
- + "for rescaling.",
- timeWindow,
- desiredTimeScale.getPeriod(),
- adjustedWindow );
+ LOGGER.debug( "Adjusting the lower valid datetime of time window {} from {} to {} "
+ + "in order to acquire data at the desired timescale of {}.",
+ timeWindow,
+ timeWindow.getEarliestValidTime(),
+ lowered,
+ timeScale );
}
+
+ adjusted.setEarliestValidTime( MessageFactory.getTimestamp( lowered ) );
}
- return adjustedWindow;
+ return TimeWindowOuter.of( adjusted.build() );
}
/**
diff --git a/wres-datamodel/test/wres/datamodel/time/TimeSeriesSlicerTest.java b/wres-datamodel/test/wres/datamodel/time/TimeSeriesSlicerTest.java
index 2f253711f0..5b14947f95 100644
--- a/wres-datamodel/test/wres/datamodel/time/TimeSeriesSlicerTest.java
+++ b/wres-datamodel/test/wres/datamodel/time/TimeSeriesSlicerTest.java
@@ -1472,12 +1472,12 @@ void testAdjustByTimeScalePeriodWhenTimeScaleIsInstantaneous()
TimeScaleOuter timeScaleOuter = TimeScaleOuter.of( timeScale );
TimeWindowOuter timeWindowOuter = TimeWindowOuter.of( timeWindow );
- TimeWindowOuter actual = TimeSeriesSlicer.adjustByTimeScalePeriod( timeWindowOuter, timeScaleOuter );
+ TimeWindowOuter actual = TimeSeriesSlicer.adjustTimeWindowForTimeScale( timeWindowOuter, timeScaleOuter );
assertEquals( timeWindowOuter, actual );
}
@Test
- void testAdjustByTimeScalePeriod()
+ void testAdjustTimeWindowEarliestLeadDurationForTimeScale()
{
TimeWindow timeWindow = MessageFactory.getTimeWindow( Duration.ofHours( 1 ),
Duration.ofHours( 2 ) );
@@ -1488,7 +1488,7 @@ void testAdjustByTimeScalePeriod()
TimeScaleOuter timeScaleOuter = TimeScaleOuter.of( timeScale );
TimeWindowOuter timeWindowOuter = TimeWindowOuter.of( timeWindow );
- TimeWindowOuter actual = TimeSeriesSlicer.adjustByTimeScalePeriod( timeWindowOuter, timeScaleOuter );
+ TimeWindowOuter actual = TimeSeriesSlicer.adjustTimeWindowForTimeScale( timeWindowOuter, timeScaleOuter );
TimeWindow expectedInner = MessageFactory.getTimeWindow( Duration.ofMinutes( 30 ),
Duration.ofHours( 2 ) );
TimeWindowOuter expected = TimeWindowOuter.of( expectedInner );
@@ -1496,6 +1496,28 @@ void testAdjustByTimeScalePeriod()
assertEquals( expected, actual );
}
+ @Test
+ void testAdjustTimeWindowEarliestValidTimeForTimeScale()
+ {
+ Instant earliest = Instant.parse( "2055-03-23T00:00:00Z" );
+ Instant latest = Instant.parse( "2055-03-24T00:00:00Z" );
+
+ TimeWindow timeWindow = MessageFactory.getTimeWindow( earliest, latest );
+ TimeScale timeScale = TimeScale.newBuilder()
+ .setPeriod( com.google.protobuf.Duration.newBuilder()
+ .setSeconds( 86400 ) )
+ .build();
+ TimeScaleOuter timeScaleOuter = TimeScaleOuter.of( timeScale );
+ TimeWindowOuter timeWindowOuter = TimeWindowOuter.of( timeWindow );
+
+ TimeWindowOuter actual = TimeSeriesSlicer.adjustTimeWindowForTimeScale( timeWindowOuter, timeScaleOuter );
+ TimeWindow expectedInner = MessageFactory.getTimeWindow( Instant.parse( "2055-03-22T00:00:00Z" ),
+ latest );
+ TimeWindowOuter expected = TimeWindowOuter.of( expectedInner );
+
+ assertEquals( expected, actual );
+ }
+
@Test
void testGetTimesteps()
{
diff --git a/wres-io/src/wres/io/retrieving/RetrieverUtilities.java b/wres-io/src/wres/io/retrieving/RetrieverUtilities.java
index d258fcad03..724e98a3d4 100644
--- a/wres-io/src/wres/io/retrieving/RetrieverUtilities.java
+++ b/wres-io/src/wres/io/retrieving/RetrieverUtilities.java
@@ -185,8 +185,8 @@ public static TimeWindowOuter getTimeWindowWithUnconditionalLeadTimes( TimeWindo
.setLatestLeadDuration( upper )
.build();
- return TimeSeriesSlicer.adjustByTimeScalePeriod( TimeWindowOuter.of( inner ),
- timeScale );
+ return TimeSeriesSlicer.adjustTimeWindowForTimeScale( TimeWindowOuter.of( inner ),
+ timeScale );
}
/**
diff --git a/wres-io/src/wres/io/retrieving/database/TimeSeriesRetriever.java b/wres-io/src/wres/io/retrieving/database/TimeSeriesRetriever.java
index 275d6ead0f..f069ab3eab 100644
--- a/wres-io/src/wres/io/retrieving/database/TimeSeriesRetriever.java
+++ b/wres-io/src/wres/io/retrieving/database/TimeSeriesRetriever.java
@@ -38,6 +38,7 @@
import wres.datamodel.time.TimeSeries;
import wres.datamodel.time.TimeSeriesMetadata;
+import wres.datamodel.time.TimeSeriesSlicer;
import wres.datamodel.time.TimeWindowOuter;
import wres.datamodel.DataProvider;
import wres.io.database.caching.Features;
@@ -46,10 +47,8 @@
import wres.io.database.Database;
import wres.io.retrieving.Retriever;
import wres.io.retrieving.DataAccessException;
-import wres.statistics.MessageFactory;
import wres.statistics.generated.TimeScale.TimeScaleFunction;
import wres.statistics.generated.ReferenceTime.ReferenceTimeType;
-import wres.statistics.generated.TimeWindow;
/**
*
Abstract base class for retrieving {@link TimeSeries} from a database.
@@ -525,7 +524,7 @@ void addTimeWindowClause( DataScripter script )
// Subtract any non-instantaneous desired timescale period from the lower bound of the lead duration and
// valid time
- filter = TimeSeriesRetriever.adjustTimeWindowForTimeScale( filter, this.desiredTimeScale );
+ filter = TimeSeriesSlicer.adjustTimeWindowForTimeScale( filter, this.desiredTimeScale );
// Forecasts?
if ( this.isForecast() )
@@ -1316,84 +1315,6 @@ private void addEventToTimeSeries( Event event, TimeSeries.Builder bui
}
}
- /**
- * Subtracts any non-instantaneous desired timescale from the lower bound of the lead duration and valid time to
- * ensure that sufficient data is retrieved for upscaling.
- *
- * @param timeWindow the time window to adjust
- * @param timeScale the timescale to use
- * @return the adjusted time window
- */
-
- private static TimeWindowOuter adjustTimeWindowForTimeScale( TimeWindowOuter timeWindow,
- TimeScaleOuter timeScale )
- {
- TimeWindow.Builder adjusted = timeWindow.getTimeWindow()
- .toBuilder();
-
- // Earliest lead duration
- if ( !timeWindow.getEarliestLeadDuration()
- .equals( TimeWindowOuter.DURATION_MIN ) )
- {
- Duration period = Duration.ZERO;
-
- // Adjust the lower bound of the lead duration window by the non-instantaneous desired timescale
- if ( Objects.nonNull( timeScale )
- && !timeScale.isInstantaneous() )
- {
- period = TimeScaleOuter.getOrInferPeriodFromTimeScale( timeScale );
- }
-
- Duration lowered = timeWindow.getEarliestLeadDuration()
- .minus( period );
-
- if ( Objects.nonNull( timeScale )
- && LOGGER.isDebugEnabled() )
- {
- LOGGER.debug( "Adjusting the lower lead duration of time window {} from {} to {} "
- + "in order to acquire data at the desired timescale of {}.",
- timeWindow,
- timeWindow.getEarliestLeadDuration(),
- lowered,
- timeScale );
- }
-
- adjusted.setEarliestLeadDuration( MessageFactory.getDuration( lowered ) );
- }
-
- // Earliest valid time
- if ( !timeWindow.getEarliestValidTime()
- .equals( Instant.MIN ) )
- {
- Duration period = Duration.ZERO;
-
- // Adjust the lower bound of the lead duration window by the non-instantaneous desired timescale
- if ( Objects.nonNull( timeScale )
- && !timeScale.isInstantaneous() )
- {
- period = TimeScaleOuter.getOrInferPeriodFromTimeScale( timeScale );
- }
-
- Instant lowered = timeWindow.getEarliestValidTime()
- .minus( period );
-
- if ( Objects.nonNull( timeScale )
- && LOGGER.isDebugEnabled() )
- {
- LOGGER.debug( "Adjusting the lower valid datetime of time window {} from {} to {} "
- + "in order to acquire data at the desired timescale of {}.",
- timeWindow,
- timeWindow.getEarliestValidTime(),
- lowered,
- timeScale );
- }
-
- adjusted.setEarliestValidTime( MessageFactory.getTimestamp( lowered ) );
- }
-
- return TimeWindowOuter.of( adjusted.build() );
- }
-
/**
* Adds the lead duration bounds (if any) to the script. The interval is left-closed.
*
@@ -1603,25 +1524,28 @@ private Instant getOrInferLowerValidTime( TimeWindowOuter timeWindow )
Instant lowerValidTime = Instant.MIN;
// Lower bound present
- if ( !timeWindow.getEarliestValidTime().equals( Instant.MIN ) )
+ if ( !timeWindow.getEarliestValidTime()
+ .equals( Instant.MIN ) )
{
lowerValidTime = timeWindow.getEarliestValidTime();
}
- // Make a best effort to infer the valid times from any forecast information
+ // Make the best effort to infer the valid times from any forecast information
else
{
// Lower reference time available?
- if ( !timeWindow.getEarliestReferenceTime().equals( Instant.MIN ) )
+ if ( !timeWindow.getEarliestReferenceTime()
+ .equals( Instant.MIN ) )
{
// Use the lower reference time
lowerValidTime = timeWindow.getEarliestReferenceTime();
// Adjust for the earliest lead duration
- if ( !timeWindow.getEarliestLeadDuration().equals( TimeWindowOuter.DURATION_MIN ) )
+ if ( !timeWindow.getEarliestLeadDuration()
+ .equals( TimeWindowOuter.DURATION_MIN ) )
{
lowerValidTime = lowerValidTime.plus( timeWindow.getEarliestLeadDuration() );
- //Adjust for the desired timescale, if available
+ // Adjust for the desired timescale, if available
Duration period = Duration.ZERO;
if ( Objects.nonNull( this.desiredTimeScale ) )
diff --git a/wres-io/src/wres/io/retrieving/memory/EnsembleRetrieverFactoryInMemory.java b/wres-io/src/wres/io/retrieving/memory/EnsembleRetrieverFactoryInMemory.java
index 26d712f400..056a6efa1c 100644
--- a/wres-io/src/wres/io/retrieving/memory/EnsembleRetrieverFactoryInMemory.java
+++ b/wres-io/src/wres/io/retrieving/memory/EnsembleRetrieverFactoryInMemory.java
@@ -126,8 +126,8 @@ public Supplier