Skip to content

Commit

Permalink
Merge pull request #370 from NOAA-OWP/issue369
Browse files Browse the repository at this point in the history
Adjust the earliest valid time to account for the timescale period wh…
  • Loading branch information
james-d-brown authored Nov 26, 2024
2 parents a424047 + 2da40a3 commit b735d54
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 b735d54

Please sign in to comment.