Skip to content

Commit

Permalink
Adjust the earliest valid time to account for the timescale period wh…
Browse files Browse the repository at this point in the history
…en conducting an evaluation in memory, #369
  • Loading branch information
james-d-brown committed Nov 26, 2024
1 parent a424047 commit 2da40a3
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 138 deletions.
93 changes: 58 additions & 35 deletions wres-datamodel/src/wres/datamodel/time/TimeSeriesSlicer.java
Original file line number Diff line number Diff line change
Expand Up @@ -1616,58 +1616,81 @@ public static <S> TimeSeries<S> snip( TimeSeries<S> 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() );
}

/**
Expand Down
28 changes: 25 additions & 3 deletions wres-datamodel/test/wres/datamodel/time/TimeSeriesSlicerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) );
Expand All @@ -1488,14 +1488,36 @@ 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 );

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()
{
Expand Down
4 changes: 2 additions & 2 deletions wres-io/src/wres/io/retrieving/RetrieverUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}

/**
Expand Down
96 changes: 10 additions & 86 deletions wres-io/src/wres/io/retrieving/database/TimeSeriesRetriever.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/**
* <p>Abstract base class for retrieving {@link TimeSeries} from a database.
Expand Down Expand Up @@ -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() )
Expand Down Expand Up @@ -1316,84 +1315,6 @@ private <S> void addEventToTimeSeries( Event<S> event, TimeSeries.Builder<S> 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.
*
Expand Down Expand Up @@ -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 ) )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ public Supplier<Stream<TimeSeries<Ensemble>>> getRightRetriever( Set<Feature> fe
Objects.requireNonNull( features );
Objects.requireNonNull( timeWindow );

TimeWindowOuter adjustedWindow = TimeSeriesSlicer.adjustByTimeScalePeriod( timeWindow,
this.project.getDesiredTimeScale() );
TimeWindowOuter adjustedWindow = TimeSeriesSlicer.adjustTimeWindowForTimeScale( timeWindow,
this.project.getDesiredTimeScale() );

Dataset data = DeclarationUtilities.getDeclaredDataset( this.project.getDeclaration(),
DatasetOrientation.RIGHT );
Expand Down Expand Up @@ -169,8 +169,8 @@ public Supplier<Stream<TimeSeries<Ensemble>>> getBaselineRetriever( Set<Feature>
Objects.requireNonNull( features );
Objects.requireNonNull( timeWindow );

TimeWindowOuter adjustedWindow = TimeSeriesSlicer.adjustByTimeScalePeriod( timeWindow,
this.project.getDesiredTimeScale() );
TimeWindowOuter adjustedWindow = TimeSeriesSlicer.adjustTimeWindowForTimeScale( timeWindow,
this.project.getDesiredTimeScale() );

Dataset data = DeclarationUtilities.getDeclaredDataset( this.project.getDeclaration(),
DatasetOrientation.BASELINE );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ public Supplier<Stream<TimeSeries<Double>>> getLeftRetriever( Set<Feature> featu
public Supplier<Stream<TimeSeries<Ensemble>>> getRightRetriever( Set<Feature> features,
TimeWindowOuter timeWindow )
{
TimeWindowOuter adjustedWindow = TimeSeriesSlicer.adjustByTimeScalePeriod( timeWindow,
this.project.getDesiredTimeScale() );
TimeWindowOuter adjustedWindow = TimeSeriesSlicer.adjustTimeWindowForTimeScale( timeWindow,
this.project.getDesiredTimeScale() );

Dataset data = DeclarationUtilities.getDeclaredDataset( this.project.getDeclaration(),
DatasetOrientation.RIGHT );
Expand Down Expand Up @@ -169,8 +169,8 @@ public Supplier<Stream<TimeSeries<Double>>> getBaselineRetriever( Set<Feature> f
public Supplier<Stream<TimeSeries<Double>>> getBaselineRetriever( Set<Feature> features,
TimeWindowOuter timeWindow )
{
TimeWindowOuter adjustedWindow = TimeSeriesSlicer.adjustByTimeScalePeriod( timeWindow,
this.project.getDesiredTimeScale() );
TimeWindowOuter adjustedWindow = TimeSeriesSlicer.adjustTimeWindowForTimeScale( timeWindow,
this.project.getDesiredTimeScale() );

Dataset data = DeclarationUtilities.getDeclaredDataset( this.project.getDeclaration(),
DatasetOrientation.BASELINE );
Expand Down
Loading

0 comments on commit 2da40a3

Please sign in to comment.