Skip to content

Commit

Permalink
Support the declaration of event detection, #130
Browse files Browse the repository at this point in the history
  • Loading branch information
james-d-brown committed Nov 29, 2024
1 parent b735d54 commit b648481
Show file tree
Hide file tree
Showing 16 changed files with 469 additions and 56 deletions.
20 changes: 13 additions & 7 deletions test/wres/pipeline/pooling/CovariateFilterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;

import wres.config.yaml.components.CovariateDataset;
import wres.config.yaml.components.CovariateDatasetBuilder;
import wres.config.yaml.components.DataType;
import wres.config.yaml.components.Dataset;
import wres.config.yaml.components.DatasetBuilder;
Expand Down Expand Up @@ -52,8 +53,10 @@ void testUnconditionalFilter()
.name( "foo" )
.build() )
.build();
CovariateDataset covariateDataset =
new CovariateDataset( covariateData, null, null, DatasetOrientation.LEFT, null );
CovariateDataset covariateDataset = CovariateDatasetBuilder.builder()
.dataset( covariateData )
.featureNameOrientation( DatasetOrientation.LEFT )
.build();

// Unconditional filter
Predicate<Double> filter = d -> true;
Expand Down Expand Up @@ -149,8 +152,10 @@ void testFilterWithTimeShift()
// Add 4 hours
.timeShift( Duration.ofHours( 4 ) )
.build();
CovariateDataset covariateDataset =
new CovariateDataset( covariateData, null, null, DatasetOrientation.LEFT, null );
CovariateDataset covariateDataset = CovariateDatasetBuilder.builder()
.dataset( covariateData )
.featureNameOrientation( DatasetOrientation.LEFT )
.build();

Predicate<Double> filter = d -> d > 0.5;

Expand Down Expand Up @@ -250,9 +255,10 @@ void testFilterWithUpscaling()
.build() )
.timeScale( existingTimeScale )
.build();
CovariateDataset covariateDataset =
new CovariateDataset( covariateData, null, null, DatasetOrientation.LEFT, null );

CovariateDataset covariateDataset = CovariateDatasetBuilder.builder()
.dataset( covariateData )
.featureNameOrientation( DatasetOrientation.LEFT )
.build();
Predicate<Double> filter = d -> d > 0.5;

TimeScaleOuter desiredTimeScale = TimeScaleOuter.of( Duration.ofHours( 2 ),
Expand Down
52 changes: 49 additions & 3 deletions wres-config/nonsrc/schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ definitions:
"$ref": "#/definitions/Dates"
time_pools:
"$ref": "#/definitions/TimePools"
event_detection:
anyOf:
- "$ref": "#/definitions/EventDetection"
- "$ref": "#/definitions/DatasetEnum"
reference_date_pools:
"$ref": "#/definitions/TimePoolSequence"
valid_dates:
Expand Down Expand Up @@ -381,21 +385,31 @@ definitions:

Covariate:
title: An evaluation covariate.
description: "A covariate is cross-paired with the main variates (observed,
predicted and baseline) and used to filter these variates."
description: "A covariate is an associated source of time-series data, which
may be used to filter the main variates (observed, predicted and baseline)
or conduct event detection."
"$ref": "#/definitions/BasicDataset"
# Close this schema to unevaluated properties
unevaluatedProperties: false
properties:
# The minimum value of the covariate
minimum:
type: number
# The minimum value of the covariate
# The maximum value of the covariate
maximum:
type: number
# Target time-scale function, if not the evaluation time-scale function
rescale_function:
"$ref": "#/definitions/TimeScaleFunctionEnum"
# Purpose(s) of the covariate
purpose:
anyOf:
- "$ref": "#/definitions/CovariatePurposeEnum"
- type: array
items:
"$ref": "#/definitions/CovariatePurposeEnum"
uniqueItems: true
minItems: 1

Source:
title: A data source.
Expand Down Expand Up @@ -560,6 +574,24 @@ definitions:
- period
- unit

EventDetection:
title: Time-series event detection
description: "Performs time-series event detection using a declared dataset
and optional detection parameters."
type: object
additionalProperties: false
properties:
dataset:
anyOf:
- "$ref": "#/definitions/DatasetEnum"
- type: array
items:
"$ref": "#/definitions/DatasetEnum"
uniqueItems: true
minItems: 1
required:
- dataset

CrossPair:
title: Cross-pairing of time-series for consistency
description: "Applies cross-pairing to the time-series within an evaluation
Expand Down Expand Up @@ -1490,6 +1522,20 @@ definitions:
- minimum
- maximum

CovariatePurposeEnum:
type: string
enum:
- filter
- detect

DatasetEnum:
type: string
enum:
- observed
- predicted
- baseline
- covariates

TimeScaleLenienceEnum:
title: The lenience to apply when rescaling time-series.
description: "The side(s) of data where lenience should be applied when
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package wres.config.yaml.components;

import java.util.Objects;
import java.util.Set;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
Expand All @@ -16,14 +17,16 @@
* @param maximum the maximum value, optional
* @param featureNameOrientation the orientation of the feature names used by the covariate dataset, optional
* @param rescaleFunction the timescale function to use when it differs from the evaluation timescale function
* @param purposes the purposes or applications of the covariate dataset, optional
*/
@RecordBuilder
@JsonDeserialize( using = CovariateDatasetDeserializer.class )
public record CovariateDataset( Dataset dataset,
@JsonProperty( "minimum" ) Double minimum,
@JsonProperty( "maximum" ) Double maximum,
DatasetOrientation featureNameOrientation,
@JsonProperty( "rescale_function" ) TimeScale.TimeScaleFunction rescaleFunction )
@JsonProperty( "rescale_function" ) TimeScale.TimeScaleFunction rescaleFunction,
@JsonProperty( "purpose" ) Set<CovariatePurpose> purposes )
{
/**
* Creates an instance.
Expand All @@ -32,9 +35,16 @@ public record CovariateDataset( Dataset dataset,
* @param maximum the maximum value, optional
* @param featureNameOrientation the orientation of the feature names used by the covariate dataset, optional
* @param rescaleFunction the timescale function to use when it differs from the evaluation timescale function
* @param purposes the purposes or applications of the covariate dataset, optional
*/
public CovariateDataset
{
Objects.requireNonNull( dataset, "The covariate dataset cannot be null." );

if ( Objects.isNull( purposes )
|| purposes.isEmpty() )
{
purposes = Set.of( CovariatePurpose.FILTER );
}
}
}
15 changes: 15 additions & 0 deletions wres-config/src/wres/config/yaml/components/CovariatePurpose.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package wres.config.yaml.components;

import com.fasterxml.jackson.annotation.JsonProperty;

/**
* The purpose or application of a covariate dataset within an evaluation.
* @author James Brown
*/
public enum CovariatePurpose
{
/** Use the covariate dataset to filter the pairs. */
@JsonProperty( "filter" ) FILTER,
/** Use the covariate dataset to detect events for evaluation. */
@JsonProperty( "detect" ) DETECT
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
* @param leadTimes lead times
* @param analysisTimes analysis durations
* @param leadTimePools lead time pools
* @param eventDetection event detection
* @param timeScale the evaluation timescale
* @param rescaleLenience whether rescaling should admit periods with missing values
* @param pairFrequency the frequency of the paired data
Expand Down Expand Up @@ -98,6 +99,7 @@ public record EvaluationDeclaration( @JsonProperty( "label" ) String label,
@JsonProperty( "valid_date_pools" ) TimePools validDatePools,
@JsonProperty( "lead_times" ) LeadTimeInterval leadTimes,
@JsonProperty( "lead_time_pools" ) TimePools leadTimePools,
@JsonProperty( "event_detection" ) EventDetection eventDetection,
@JsonProperty( "analysis_times" ) AnalysisTimes analysisTimes,
@JsonProperty( "time_scale" ) TimeScale timeScale,
@JsonProperty( "rescale_lenience" ) TimeScaleLenience rescaleLenience,
Expand Down Expand Up @@ -161,6 +163,7 @@ public record EvaluationDeclaration( @JsonProperty( "label" ) String label,
* @param leadTimes lead times
* @param analysisTimes analysis durations
* @param leadTimePools lead time pools
* @param eventDetection event detection
* @param timeScale the evaluation timescale
* @param rescaleLenience whether rescaling should admit periods with missing values
* @param pairFrequency the frequency of the paired data
Expand Down
33 changes: 33 additions & 0 deletions wres-config/src/wres/config/yaml/components/EventDetection.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package wres.config.yaml.components;

import java.util.Objects;
import java.util.Set;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.soabase.recordbuilder.core.RecordBuilder;

import wres.config.yaml.deserializers.EventDetectionDeserializer;

/**
* Used for the detection of discrete events within time-series.
* @param datasets the datasets to use for event detection
*/
@RecordBuilder
@JsonDeserialize( using = EventDetectionDeserializer.class )
public record EventDetection( @JsonProperty( "dataset" ) Set<EventDetectionDataset> datasets )
{
/**
* Creates an instance.
* @param datasets the datasets to use for event detection
*/
public EventDetection
{
Objects.requireNonNull( datasets, "The event detection dataset cannot be null." );

if ( datasets.isEmpty() )
{
throw new IllegalArgumentException( "Declare at least one dataset for event detection." );
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package wres.config.yaml.components;

import com.fasterxml.jackson.annotation.JsonProperty;

/**
* The context of a dataset to use for event detection.
* @author James Brown
*/
public enum EventDetectionDataset
{
/** Observed dataset. */
@JsonProperty( "observed" ) OBSERVED,
/** Predicted dataset. */
@JsonProperty( "predicted" ) PREDICTED,
/** Baseline dataset. */
@JsonProperty( "baseline" ) BASELINE,
/** Covariates dataset. */
@JsonProperty( "covariates" ) COVARIATES
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package wres.config.yaml.deserializers;

import java.io.IOException;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectReader;

import wres.config.yaml.components.CovariateDataset;
import wres.config.yaml.components.CovariateDatasetBuilder;
import wres.config.yaml.components.CovariatePurpose;
import wres.config.yaml.components.Dataset;
import wres.config.yaml.components.DatasetOrientation;
import wres.statistics.generated.TimeScale;
Expand All @@ -32,6 +38,7 @@ public CovariateDataset deserialize( JsonParser jp, DeserializationContext conte
Double minimum = null;
Double maximum = null;
TimeScale.TimeScaleFunction rescaleFunction = null;
Set<CovariatePurpose> purposes = Collections.singleton( CovariatePurpose.FILTER );

// Not part of the declaration language, just used internally
DatasetOrientation featureNameOrientation = null;
Expand Down Expand Up @@ -59,8 +66,35 @@ public CovariateDataset deserialize( JsonParser jp, DeserializationContext conte
.toUpperCase();
rescaleFunction = TimeScale.TimeScaleFunction.valueOf( functionString );
}

if ( lastNode.has( "purpose" ) )
{
JsonNode purposeNode = lastNode.get( "purpose" );

if ( purposeNode.isTextual() )
{
String purposeString = purposeNode.asText()
.toUpperCase();
CovariatePurpose purposeEnum = CovariatePurpose.valueOf( purposeString );
purposes = Collections.singleton( purposeEnum );
}
else if ( purposeNode.isArray() )
{
ObjectReader reader = ( ObjectReader ) jp.getCodec();
JavaType type = reader.getTypeFactory()
.constructCollectionType( Set.class, CovariatePurpose.class );
JsonParser parser = reader.treeAsTokens( purposeNode );
purposes = reader.readValue( parser, type );
}
}
}

return new CovariateDataset( basicDataset, minimum, maximum, featureNameOrientation, rescaleFunction );
return CovariateDatasetBuilder.builder().dataset( basicDataset )
.minimum( minimum )
.maximum( maximum )
.featureNameOrientation( featureNameOrientation )
.rescaleFunction( rescaleFunction )
.purposes( purposes )
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,9 @@ private EnsembleFilter getEnsembleFilter( ObjectReader reader, JsonNode node ) t
{
JsonNode memberNode = filterNode.get( "members" );
members = this.getMembers( reader, memberNode );
exclude = filterNode.has( "exclude" ) && filterNode.get( "exclude" )
.asBoolean();
exclude = filterNode.has( "exclude" )
&& filterNode.get( "exclude" )
.asBoolean();
}

// Ordinary member declaration
Expand Down
Loading

0 comments on commit b648481

Please sign in to comment.