diff --git a/wres-datamodel/src/wres/datamodel/bootstrap/BootstrapPool.java b/wres-datamodel/src/wres/datamodel/bootstrap/BootstrapPool.java index 388ffc586d..158e7fd0d8 100644 --- a/wres-datamodel/src/wres/datamodel/bootstrap/BootstrapPool.java +++ b/wres-datamodel/src/wres/datamodel/bootstrap/BootstrapPool.java @@ -128,58 +128,99 @@ Set getValidTimeOffsets() { if ( next.size() > 1 ) { - // Offsets between consecutive series - for ( int i = 1; i < next.size(); i++ ) - { - Instant first; - List> firstSeries = next.get( i - 1 ); - - if ( this.hasForecasts() ) - { - first = firstSeries.get( 0 ) - .getTime(); - } - else - { - first = firstSeries.get( firstSeries.size() - 1 ) - .getTime(); - } - - Instant last = next.get( i ) - .get( 0 ) - .getTime(); - Duration offset = Duration.between( first, last ) - .abs(); - offsets.add( offset ); - } + Set nextOffsets = this.getOffsetsBetweenConsecutiveSeries( next ); + offsets.addAll( nextOffsets ); + } + } + + return Collections.unmodifiableSet( offsets ); + } + + /** + * Adds the time offset between adjacent time-series in the inputs. + * @param series the series + */ + private Set getOffsetsBetweenConsecutiveSeries( List>> series ) + { + Set offsets = new HashSet<>(); - // Offset between the first and last series, which is required because the resampling is circular - Instant first = next.get( 0 ) - .get( 0 ) - .getTime(); - List> lastSeries = next.get( next.size() - 1 ); - Instant last; + // Offsets between consecutive series + for ( int i = 1; i < series.size(); i++ ) + { + Instant first; + List> firstSeries = series.get( i - 1 ); + if ( !firstSeries.isEmpty() ) + { if ( this.hasForecasts() ) { - last = lastSeries.get( 0 ) - .getTime(); + first = firstSeries.get( 0 ) + .getTime(); } else { - last = lastSeries.get( lastSeries.size() - 1 ) - .getTime(); + first = firstSeries.get( firstSeries.size() - 1 ) + .getTime(); } + Instant last = series.get( i ) + .get( 0 ) + .getTime(); Duration offset = Duration.between( first, last ) .abs(); offsets.add( offset ); } } + // Offset between the first and last series, which is required because the resampling is circular + Duration offset = this.getOffsetBetweenFirstAndLastSeries( series ); + if ( Objects.nonNull( offset ) ) + { + offsets.add( offset ); + } + return Collections.unmodifiableSet( offsets ); } + /** + * Gets the time offset between the first and last series. + * @param series the series + */ + private Duration getOffsetBetweenFirstAndLastSeries( List>> series ) + { + // Offset between the first and last series, which is required because the resampling is circular + List> firstSeries = series.get( 0 ); + List> lastSeries = series.get( series.size() - 1 ); + + Duration offset = null; + + if ( !firstSeries.isEmpty() + && !lastSeries.isEmpty() ) + { + Instant first = firstSeries + .get( 0 ) + .getTime(); + + Instant last; + + if ( this.hasForecasts() ) + { + last = lastSeries.get( 0 ) + .getTime(); + } + else + { + last = lastSeries.get( lastSeries.size() - 1 ) + .getTime(); + } + + offset = Duration.between( first, last ) + .abs(); + } + + return offset; + } + /** * @return the original pool */ @@ -218,8 +259,12 @@ private BootstrapPool( Pool> pool ) List>> groupedBySize = new ArrayList<>(); for ( Map.Entry>> nextEntry : bySize.entrySet() ) { + int count = nextEntry.getKey(); List> nextList = nextEntry.getValue(); - nextList.sort( sorter ); + if ( count > 0 ) + { + nextList.sort( sorter ); + } groupedBySize.add( nextList ); } @@ -237,7 +282,10 @@ private BootstrapPool( Pool> pool ) List> nextInner = nextInnerEntry.getValue(); // Sort by the first valid time in each series - nextInner.sort( sorter ); + if ( nextCount > 0 ) + { + nextInner.sort( sorter ); + } List>> unwrapped = nextInner.stream() .map( n -> List.copyOf( n.getEvents() ) )