Skip to content

Commit

Permalink
Merge pull request #421 from NOAA-OWP/issue420
Browse files Browse the repository at this point in the history
Ignore and warn about the declaration of combination parameters when …
  • Loading branch information
james-d-brown authored Feb 13, 2025
2 parents 52f9191 + e828154 commit 4df6e16
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 52 deletions.
97 changes: 60 additions & 37 deletions src/wres/pipeline/pooling/EventsGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Set<TimeWindowOuter> doEventDetection( Project project,
Set<TimeWindowOuter> events = new HashSet<>();
TimeScaleOuter desiredTimeScale = project.getDesiredTimeScale();

boolean detectionAttempted = false; // Only attempt to combine events when detection has been attempted
int detectionAttemptedCount = 0; // Only attempt to combine events when detection has been attempted
for ( EventDetectionDataset dataset : detection.datasets() )
{
switch ( dataset )
Expand Down Expand Up @@ -195,8 +195,8 @@ Set<TimeWindowOuter> doEventDetection( Project project,
+ "orientation." );
}

this.combineEvents( detectionAttempted, events, innerEvents, combination );
detectionAttempted = true;
this.combineEvents( detectionAttemptedCount > 0, events, innerEvents, combination );
detectionAttemptedCount++;
}
}
case OBSERVED ->
Expand All @@ -215,8 +215,8 @@ Set<TimeWindowOuter> doEventDetection( Project project,
this.measurementUnit(),
declaration );
Set<TimeWindowOuter> innerEvents = this.doEventDetection( details );
this.combineEvents( detectionAttempted, events, innerEvents, combination );
detectionAttempted = true;
this.combineEvents( detectionAttemptedCount > 0, events, innerEvents, combination );
detectionAttemptedCount++;
}
case PREDICTED ->
{
Expand All @@ -234,8 +234,8 @@ Set<TimeWindowOuter> doEventDetection( Project project,
this.measurementUnit(),
declaration );
Set<TimeWindowOuter> innerEvents = this.doEventDetection( details );
this.combineEvents( detectionAttempted, events, innerEvents, combination );
detectionAttempted = true;
this.combineEvents( detectionAttemptedCount > 0, events, innerEvents, combination );
detectionAttemptedCount++;
}
case BASELINE ->
{
Expand All @@ -253,8 +253,8 @@ Set<TimeWindowOuter> doEventDetection( Project project,
this.measurementUnit(),
declaration );
Set<TimeWindowOuter> innerEvents = this.doEventDetection( details );
this.combineEvents( detectionAttempted, events, innerEvents, combination );
detectionAttempted = true;
this.combineEvents( detectionAttemptedCount > 0, events, innerEvents, combination );
detectionAttemptedCount++;
}
}
}
Expand All @@ -264,11 +264,15 @@ Set<TimeWindowOuter> doEventDetection( Project project,
featureGroup.getName(),
combination );

// Aggregate any intersecting events, as needed
Set<TimeWindowOuter> aggregated = this.aggregateEvents( events,
detection.parameters()
.aggregation(),
featureGroup );
// Aggregate any intersecting events, as needed, but only if combination/intersection happened
Set<TimeWindowOuter> aggregated = events;
if ( detectionAttemptedCount > 1 ) // Combination/intersection happened
{
aggregated = this.aggregateEvents( events,
detection.parameters()
.aggregation(),
featureGroup );
}

// Adjust the time windows to account for existing time windows and other explicit time constraints
Set<TimeWindowOuter> declared = TimeWindowSlicer.getTimeWindows( declaration );
Expand Down Expand Up @@ -336,9 +340,9 @@ private Set<TimeWindowOuter> doEventDetection( EventDetectionDetails details )
Stream<TimeSeries<Double>> series = eventRetriever.getLeftRetriever( features, timeWindow )
.get();

Set<TimeWindowOuter> innerEvents = series.flatMap( s -> this.doEventDetection( s,
details,
this.leftUpscaler() )
Set<TimeWindowOuter> innerEvents = series.flatMap( s -> this.adjustTimeSeriesAndDetectEvents( s,
details,
this.leftUpscaler() )
.stream() )
.collect( Collectors.toSet() );
LOGGER.info( DETECTED_EVENTS_IN_THE_DATASET,
Expand All @@ -352,9 +356,9 @@ private Set<TimeWindowOuter> doEventDetection( EventDetectionDetails details )
Stream<TimeSeries<Double>> series = eventRetriever.getRightRetriever( features, timeWindow )
.get();

Set<TimeWindowOuter> innerEvents = series.flatMap( s -> this.doEventDetection( s,
details,
this.rightUpscaler() )
Set<TimeWindowOuter> innerEvents = series.flatMap( s -> this.adjustTimeSeriesAndDetectEvents( s,
details,
this.rightUpscaler() )
.stream() )
.collect( Collectors.toSet() );
LOGGER.info( DETECTED_EVENTS_IN_THE_DATASET,
Expand All @@ -368,9 +372,9 @@ private Set<TimeWindowOuter> doEventDetection( EventDetectionDetails details )
Stream<TimeSeries<Double>> series = eventRetriever.getBaselineRetriever( features, timeWindow )
.get();

Set<TimeWindowOuter> innerEvents = series.flatMap( s -> this.doEventDetection( s,
details,
this.baselineUpscaler() )
Set<TimeWindowOuter> innerEvents = series.flatMap( s -> this.adjustTimeSeriesAndDetectEvents( s,
details,
this.baselineUpscaler() )
.stream() )
.collect( Collectors.toSet() );
LOGGER.info( DETECTED_EVENTS_IN_THE_DATASET,
Expand Down Expand Up @@ -405,10 +409,11 @@ private Set<TimeWindowOuter> doEventDetection( EventDetectionDetails details )
};
Set<TimeWindowOuter> innerEvents =
series.map( counter )
.flatMap( s -> this.doEventDetection( s,
this.getAdjustedDetails( details, s.getMetadata()
.getUnit() ),
this.covariateUpscaler() )
.flatMap( s -> this.adjustTimeSeriesAndDetectEvents( s,
this.getAdjustedDetails( details,
s.getMetadata()
.getUnit() ),
this.covariateUpscaler() )
.stream() )
.collect( Collectors.toSet() );
LOGGER.info( "Detected {} events in the {} dataset containing {} time-series for feature group {} "
Expand Down Expand Up @@ -603,16 +608,39 @@ private Set<TimeWindowOuter> aggregateEvents( Set<TimeWindowOuter> events,
}

/**
* Performs event detection.
* Performs rescaling and filtering of event values, followed by event detection.
*
* @param timeSeries the time-series
* @param details the event detection details
* @return the time windows, one for each detected event
*/

private Set<TimeWindowOuter> doEventDetection( TimeSeries<Double> timeSeries,
EventDetectionDetails details,
TimeSeriesUpscaler<Double> upscaler )
private Set<TimeWindowOuter> adjustTimeSeriesAndDetectEvents( TimeSeries<Double> timeSeries,
EventDetectionDetails details,
TimeSeriesUpscaler<Double> upscaler )
{
// Rescale the time-series if needed
TimeSeries<Double> adjusted = this.getRescaledTimeSeries( timeSeries, details, upscaler );

LOGGER.debug( "Performing event detection of a time-series dataset containing {} events and the following "
+ "metadata: {}.", adjusted.getEvents()
.size(), adjusted.getMetadata() );

return this.eventDetector()
.detect( adjusted );
}

/**
* Performs rescaling of the input time-series as needed.
*
* @param timeSeries the time-series
* @param details the event detection details
* @return the rescaled time-series
*/

private TimeSeries<Double> getRescaledTimeSeries( TimeSeries<Double> timeSeries,
EventDetectionDetails details,
TimeSeriesUpscaler<Double> upscaler )
{
// Upscale the time-series if needed
boolean upscale = Objects.nonNull( details.desiredTimeScale() )
Expand Down Expand Up @@ -640,12 +668,7 @@ private Set<TimeWindowOuter> doEventDetection( TimeSeries<Double> timeSeries,
timeSeries = upscaled.getTimeSeries();
}

LOGGER.debug( "Performing event detection of a time-series dataset containing {} events and the following "
+ "metadata: {}.", timeSeries.getEvents()
.size(), timeSeries.getMetadata() );

return this.eventDetector()
.detect( timeSeries );
return timeSeries;
}

/**
Expand Down
66 changes: 66 additions & 0 deletions test/wres/pipeline/pooling/EventsGeneratorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,72 @@ void testEventDetectionAddsDeclaredTimeConstraintsToDetectedEvent()
assertEquals( expected, actual );
}

@Test
void testEventDetectionIgnoresEventAggregationForSingleton()
{
// GitHub issue #420

TimeSeriesUpscaler<Double> upscaler = TimeSeriesOfDoubleUpscaler.of();
EventDetectionParameters parameters = EventDetectionParametersBuilder.builder()
.windowSize( Duration.ofHours( 6 ) )
.minimumEventDuration( Duration.ZERO )
.halfLife( Duration.ofHours( 2 ) )
.combination( EventDetectionCombination.INTERSECTION )
.aggregation( TimeWindowAggregation.MAXIMUM )
.build();
EventDetector detector = EventDetectorFactory.getEventDetector( EventDetectionMethod.REGINA_OGDEN,
parameters );
String measurementUnit = "qux";
EventsGenerator generator = new EventsGenerator( upscaler,
upscaler,
upscaler,
upscaler,
measurementUnit,
detector );

TimeSeries<Double> timeSeriesOne = this.getTestTimeSeriesWithOffset( Duration.ZERO );

// Shift the series by one hour, which will eliminate the first event upon intersection, leaving two in total
// of the four events detected across the two series
TimeSeries<Double> timeSeriesTwo = this.getTestTimeSeriesWithOffset( Duration.ofHours( 1 ) );

// Mock a retriever factory
Mockito.when( this.leftRetriever.get() )
.thenReturn( Stream.of( timeSeriesOne ) );
Mockito.when( this.rightRetriever.get() )
.thenReturn( Stream.of( timeSeriesTwo ) );
Mockito.when( this.retrieverFactory.getLeftRetriever( Mockito.anySet(), Mockito.any() ) )
.thenReturn( this.leftRetriever );
Mockito.when( this.retrieverFactory.getRightRetriever( Mockito.anySet(), Mockito.any() ) )
.thenReturn( this.rightRetriever );

// Mock the sufficient elements of a project with two separate datasets for event detection
EventDetection eventDeclaration = EventDetectionBuilder.builder()
.method( EventDetectionMethod.REGINA_OGDEN )
.parameters( parameters )
.datasets( Set.of( EventDetectionDataset.OBSERVED ) )
.build();

EvaluationDeclaration declaration = EvaluationDeclarationBuilder.builder()
.eventDetection( eventDeclaration )
.build();

Geometry geometry = MessageUtilities.getGeometry( "bar" );
GeometryTuple geoTuple = MessageUtilities.getGeometryTuple( geometry, geometry, geometry );
GeometryGroup geoGroup = MessageUtilities.getGeometryGroup( null, geoTuple );
FeatureGroup groupOne = FeatureGroup.of( geoGroup );

Project project = Mockito.mock( Project.class );
Mockito.when( project.getFeatureGroups() )
.thenReturn( Set.of( groupOne ) );
Mockito.when( project.getDeclaration() )
.thenReturn( declaration );

Set<TimeWindowOuter> actual = generator.doEventDetection( project, groupOne, this.retrieverFactory );

assertEquals( 2, actual.size() );
}

/**
* Generates a test time-series.
* @param offset the offset to apply
Expand Down
Loading

0 comments on commit 4df6e16

Please sign in to comment.