Skip to content

Commit

Permalink
Remove 'none' as an 'aggregation' option for event detection and make…
Browse files Browse the repository at this point in the history
… the 'operation' required when an explicit 'combination' is declared, #423.
  • Loading branch information
james-d-brown committed Feb 18, 2025
1 parent 4df6e16 commit 13188d2
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 49 deletions.
98 changes: 58 additions & 40 deletions src/wres/pipeline/pooling/EventsGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -259,15 +259,15 @@ Set<TimeWindowOuter> doEventDetection( Project project,
}
}

LOGGER.info( "Detected {} events across all datasets for feature group {} when forming the {}.",
events.size(),
featureGroup.getName(),
combination );

// Aggregate any intersecting events, as needed, but only if combination/intersection happened
Set<TimeWindowOuter> aggregated = events;
if ( detectionAttemptedCount > 1 ) // Combination/intersection happened
{
LOGGER.info( "Detected {} events across all datasets for feature group {} when forming the {}.",
events.size(),
featureGroup.getName(),
combination );

aggregated = this.aggregateEvents( events,
detection.parameters()
.aggregation(),
Expand Down Expand Up @@ -306,32 +306,14 @@ private Set<TimeWindowOuter> doEventDetection( EventDetectionDetails details )
RetrieverFactory<Double, Double, Double> eventRetriever = details.eventRetriever();

// Get any valid time constraints on retrieval, accounting for the timescale
TimeWindowOuter timeWindow = TimeWindowOuter.of( MessageUtilities.getTimeWindow() );
EvaluationDeclaration declaration = details.declaration();
TimeInterval validDates = declaration.validDates();
if ( Objects.nonNull( validDates ) )
{
Instant minimumInstant = validDates.minimum();
Instant maximumInstant = validDates.maximum();
Timestamp minimum = MessageUtilities.getTimestamp( minimumInstant );
Timestamp maximum = MessageUtilities.getTimestamp( maximumInstant );
TimeWindow adjusted = timeWindow.getTimeWindow()
.toBuilder()
.setEarliestValidTime( minimum )
.setLatestValidTime( maximum )
.build();
TimeWindowOuter adjustedOuter = TimeWindowOuter.of( adjusted );

// Adjust for timescale
timeWindow = TimeWindowSlicer.adjustTimeWindowForTimeScale( adjustedOuter, details.desiredTimeScale() );
}
TimeWindowOuter timeWindow = this.getTimeWindow( details.declaration(), details.desiredTimeScale() );

// Get the features for the dataset orientation, which is one of the main datasets (observed, predicted or
// baseline), not a covariate. The covariate features must be further derived by correlating the feature names
// whose features have the same feature authority as the covariate dataset, which is a requirement. See below.
Set<Feature> features = this.getFeatures( featureGroup.getFeatures(), featureGetter );

LOGGER.info( "Getting time-series data to perform event detection for the following features: {}", features );
LOGGER.debug( "Getting time-series data to perform event detection for the following features: {}", features );

switch ( details.dataset() )
{
Expand All @@ -340,11 +322,12 @@ private Set<TimeWindowOuter> doEventDetection( EventDetectionDetails details )
Stream<TimeSeries<Double>> series = eventRetriever.getLeftRetriever( features, timeWindow )
.get();

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

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

Set<TimeWindowOuter> innerEvents = series.flatMap( s -> this.adjustTimeSeriesAndDetectEvents( s,
details,
this.baselineUpscaler() )
.stream() )
.collect( Collectors.toSet() );
Set<TimeWindowOuter> innerEvents =
series.flatMap( s -> this.adjustTimeSeriesAndDetectEvents( s,
details,
this.baselineUpscaler() )
.stream() )
.collect( Collectors.toSet() );
LOGGER.info( DETECTED_EVENTS_IN_THE_DATASET,
innerEvents.size(),
EventDetectionDataset.BASELINE,
Expand Down Expand Up @@ -430,6 +415,39 @@ private Set<TimeWindowOuter> doEventDetection( EventDetectionDetails details )
return Collections.unmodifiableSet( events );
}

/**
* Returns the time window for retrieval of the time-series data used in event detection, accounting for any valid
* time constraints and desired time-scale.
*
* @param declaration the declaration
* @param desiredTimeScale the desired time scale
* @return the time window
*/

private TimeWindowOuter getTimeWindow( EvaluationDeclaration declaration, TimeScaleOuter desiredTimeScale )
{
TimeWindowOuter timeWindow = TimeWindowOuter.of( MessageUtilities.getTimeWindow() );
TimeInterval validDates = declaration.validDates();
if ( Objects.nonNull( validDates ) )
{
Instant minimumInstant = validDates.minimum();
Instant maximumInstant = validDates.maximum();
Timestamp minimum = MessageUtilities.getTimestamp( minimumInstant );
Timestamp maximum = MessageUtilities.getTimestamp( maximumInstant );
TimeWindow adjusted = timeWindow.getTimeWindow()
.toBuilder()
.setEarliestValidTime( minimum )
.setLatestValidTime( maximum )
.build();
TimeWindowOuter adjustedOuter = TimeWindowOuter.of( adjusted );

// Adjust for timescale
timeWindow = TimeWindowSlicer.adjustTimeWindowForTimeScale( adjustedOuter, desiredTimeScale );
}

return timeWindow;
}

/**
* Adjusts the time windows generated using event detection to reflect any declared time windows or associated
* time constraints.
Expand Down
15 changes: 8 additions & 7 deletions wres-config/nonsrc/schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,8 @@ definitions:
"$ref": "#/definitions/EventDetectionCombinationEnum"
aggregation:
"$ref": "#/definitions/EventDetectionAggregationEnum"
required:
- operation

CrossPair:
title: Cross-pairing of time-series for consistency
Expand Down Expand Up @@ -1873,15 +1875,14 @@ definitions:
EventDetectionAggregationEnum:
title: The method for aggregating events across data sources.
description: "Identifies the method for aggregating events across different
time-series data sources, such as observed and predicted. The strategy
'none' applies no aggregation. The 'maximum' forms the maximum period
across each set of intersecting events. The 'minimum' forms the minimum
period across each set of intersecting events or the temporal
intersection. The 'average' forms the average of the start datetimes and
the end datetimes for each set of intersecting events."
time-series data sources, such as observed and predicted. The 'maximum'
forms the maximum period across each set of intersecting events. The
'minimum' forms the minimum period across each set of intersecting events
or the temporal intersection. The 'average' forms the average of the
start datetimes and the end datetimes for each set of intersecting
events."
type: string
enum:
- none
- maximum
- minimum
- average
3 changes: 1 addition & 2 deletions wres-config/src/wres/config/yaml/DeclarationValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -2524,8 +2524,7 @@ private static List<EvaluationStatusEvent> eventDetectionParametersAreValid( Eva
.toString()
.toLowerCase()
+ "', which is not valid. Please remove the "
+ "'aggregation' method, declare an 'aggregation' "
+ "method of 'none' or change the 'operation' to "
+ "'aggregation' method or change the 'operation' to "
+ "'intersection'. An explicit 'aggregation' method "
+ "is only valid when the 'operation' is "
+ "'intersection'." )
Expand Down

0 comments on commit 13188d2

Please sign in to comment.