CalculatedTimeSeriesTest.java

/**
 * Copyright (c) 2018, RTE (http://www.rte-france.com)
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * SPDX-License-Identifier: MPL-2.0
 */
package com.powsybl.timeseries;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.google.common.collect.Sets;
import com.powsybl.commons.json.JsonUtil;
import com.powsybl.timeseries.ast.*;
import com.powsybl.timeseries.json.TimeSeriesJsonModule;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.threeten.extra.Interval;

import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.*;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
 */
class CalculatedTimeSeriesTest {

    private CalculatedTimeSeries timeSeries;

    @BeforeEach
    void setUp() {
        timeSeries = new CalculatedTimeSeries("ts1", new IntegerNodeCalc(1));
    }

    @Test
    void errorTest() {
        TimeSeriesException e = assertThrows(TimeSeriesException.class, () -> timeSeries.toArray());
        assertTrue(e.getMessage().contains("Impossible to fill buffer because calculated time series has not been synchronized on a finite time index"));
    }

    @Test
    void syncTest() {
        timeSeries.synchronize(RegularTimeSeriesIndex.create(Interval.parse("2015-01-01T00:00:00Z/2015-07-20T00:00:00Z"), Duration.ofDays(100)));
        assertArrayEquals(new double[] {1d, 1d, 1d}, timeSeries.toArray(), 0d);
    }

    @Test
    void splitSmallChunkTest() {
        timeSeries.synchronize(new RegularTimeSeriesIndex(Instant.ofEpochMilli(0), Instant.ofEpochMilli(99), Duration.ofMillis(1)));
        List<List<DoubleTimeSeries>> list = TimeSeries.split(Collections.singletonList(timeSeries), 50);
        assertEquals(2, list.size());
    }

    @Test
    void splitBigChunkTest() {
        timeSeries.synchronize(new RegularTimeSeriesIndex(Instant.ofEpochMilli(0), Instant.ofEpochMilli(99), Duration.ofMillis(1)));
        List<List<DoubleTimeSeries>> list = TimeSeries.split(Collections.singletonList(timeSeries), 2);
        assertEquals(50, list.size());
    }

    @Test
    void jsonTest() throws IOException {
        TimeSeriesIndex index = RegularTimeSeriesIndex.create(Interval.parse("2015-01-01T00:00:00Z/2015-07-20T00:00:00Z"), Duration.ofDays(200));
        DoubleTimeSeries ts = TimeSeries.createDouble("ts", index, 1d, 2d);
        DoubleTimeSeries foo = TimeSeries.createDouble("foo", index, 0d, 3d);
        TimeSeriesNameResolver resolver = new TimeSeriesNameResolver() {

            @Override
            public List<TimeSeriesMetadata> getTimeSeriesMetadata(Set<String> timeSeriesNames) {
                List<TimeSeriesMetadata> metadataList = new ArrayList<>(2);
                if (timeSeriesNames.contains("ts")) {
                    metadataList.add(ts.getMetadata());
                }
                if (timeSeriesNames.contains("foo")) {
                    metadataList.add(foo.getMetadata());
                }
                return metadataList;
            }

            @Override
            public Set<Integer> getTimeSeriesDataVersions(String timeSeriesName) {
                return Collections.singleton(1);
            }

            @Override
            public List<DoubleTimeSeries> getDoubleTimeSeries(Set<String> timeSeriesNames) {
                List<DoubleTimeSeries> timeSeriesList = new ArrayList<>(2);
                if (timeSeriesNames.contains("ts")) {
                    timeSeriesList.add(ts);
                }
                if (timeSeriesNames.contains("foo")) {
                    timeSeriesList.add(foo);
                }
                return timeSeriesList;
            }
        };
        CalculatedTimeSeries tsCalc = new CalculatedTimeSeries("ts_calc", BinaryOperation.plus(
            new TimeSeriesNameNodeCalc("ts"),
            new IntegerNodeCalc(1)));
        CalculatedTimeSeries tsCalcMin = new CalculatedTimeSeries("ts_calc_min", new BinaryMinCalc(
            new TimeSeriesNameNodeCalc("ts"),
            new TimeSeriesNameNodeCalc("foo")));
        CalculatedTimeSeries tsCalcMax = new CalculatedTimeSeries("ts_calc_max", new BinaryMaxCalc(
            new TimeSeriesNameNodeCalc("ts"),
            new TimeSeriesNameNodeCalc("foo")));

        // check versions of the data available for the calculated time series
        tsCalc.setTimeSeriesNameResolver(resolver);
        tsCalcMin.setTimeSeriesNameResolver(resolver);
        tsCalcMax.setTimeSeriesNameResolver(resolver);
        assertEquals(Sets.newHashSet(1), tsCalc.getVersions());

        List<DoubleTimeSeries> tsLs = Arrays.asList(ts, tsCalc, tsCalcMin, tsCalcMax);
        String json = TimeSeries.toJson(tsLs);
        String jsonRef = String.join(System.lineSeparator(),
                "[ {",
                "  \"metadata\" : {",
                "    \"name\" : \"ts\",",
                "    \"dataType\" : \"DOUBLE\",",
                "    \"tags\" : [ ],",
                "    \"regularIndex\" : {",
                "      \"startTime\" : 1420070400000,",
                "      \"endTime\" : 1437350400000,",
                "      \"spacing\" : 17280000000",
                "    }",
                "  },",
                "  \"chunks\" : [ {",
                "    \"offset\" : 0,",
                "    \"values\" : [ 1.0, 2.0 ]",
                "  } ]",
                "}, {",
                "  \"name\" : \"ts_calc\",",
                "  \"expr\" : {",
                "    \"binaryOp\" : {",
                "      \"op\" : \"PLUS\",",
                "      \"timeSeriesName\" : \"ts\",",
                "      \"integer\" : 1",
                "    }",
                "  }",
                "}, {",
                "  \"name\" : \"ts_calc_min\",",
                "  \"expr\" : {",
                "    \"binaryMin\" : {",
                "      \"timeSeriesName\" : \"ts\",",
                "      \"timeSeriesName\" : \"foo\"",
                "    }",
                "  }",
                "}, {",
                "  \"name\" : \"ts_calc_max\",",
                "  \"expr\" : {",
                "    \"binaryMax\" : {",
                "      \"timeSeriesName\" : \"ts\",",
                "      \"timeSeriesName\" : \"foo\"",
                "    }",
                "  }",
                "} ]");
        assertEquals(jsonRef, json);

        List<TimeSeries> timeSeriesList = TimeSeries.parseJson(json);
        for (TimeSeries tss : timeSeriesList) {
            tss.setTimeSeriesNameResolver(resolver);
        }

        assertEquals(4, timeSeriesList.size());
        assertInstanceOf(StoredDoubleTimeSeries.class, timeSeriesList.get(0));
        assertInstanceOf(CalculatedTimeSeries.class, timeSeriesList.get(1));
        assertInstanceOf(CalculatedTimeSeries.class, timeSeriesList.get(2));
        assertInstanceOf(CalculatedTimeSeries.class, timeSeriesList.get(3));
        assertArrayEquals(new double[] {1d, 2d}, ((DoubleTimeSeries) timeSeriesList.get(0)).toArray(), 0d);
        assertArrayEquals(new double[] {2d, 3d}, ((DoubleTimeSeries) timeSeriesList.get(1)).toArray(), 0d);
        assertArrayEquals(new double[] {0d, 2d}, ((DoubleTimeSeries) timeSeriesList.get(2)).toArray(), 0d);
        assertArrayEquals(new double[] {1d, 3d}, ((DoubleTimeSeries) timeSeriesList.get(3)).toArray(), 0d);

        // automatic jackson serialization
        ObjectMapper objectMapper = JsonUtil.createObjectMapper()
                .registerModule(new TimeSeriesJsonModule());
        List<TimeSeries> tsLs2 = objectMapper.readValue(objectMapper.writeValueAsString(tsLs),
                                                        TypeFactory.defaultInstance().constructCollectionType(List.class, TimeSeries.class));
        assertEquals(tsLs, tsLs2);
    }

    @Test
    void jsonErrorBinaryMinMaxTests() {

        // Initialisation
        TimeSeriesException e0;

        // Both parameters (left, right) are missing
        final String jsonNoParam = String.join(System.lineSeparator(),
            "[ {",
            "  \"metadata\" : {",
            "    \"name\" : \"ts\",",
            "    \"dataType\" : \"DOUBLE\",",
            "    \"tags\" : [ ],",
            "    \"regularIndex\" : {",
            "      \"startTime\" : 1420070400000,",
            "      \"endTime\" : 1437350400000,",
            "      \"spacing\" : 17280000000",
            "    }",
            "  },",
            "  \"chunks\" : [ {",
            "    \"offset\" : 0,",
            "    \"values\" : [ 1.0, 2.0 ]",
            "  } ]",
            "}, {",
            "  \"name\" : \"ts_calc_min\",",
            "  \"expr\" : {",
            "    \"binaryMin\" : {",
            "    }",
            "  }",
            "} ]");
        e0 = assertThrows(TimeSeriesException.class, () -> TimeSeries.parseJson(jsonNoParam));
        assertEquals("Invalid binary min/max node calc JSON", e0.getMessage());

        // One parameter is missing
        final String jsonOneParam = String.join(System.lineSeparator(),
            "[ {",
            "  \"metadata\" : {",
            "    \"name\" : \"ts\",",
            "    \"dataType\" : \"DOUBLE\",",
            "    \"tags\" : [ ],",
            "    \"regularIndex\" : {",
            "      \"startTime\" : 1420070400000,",
            "      \"endTime\" : 1437350400000,",
            "      \"spacing\" : 17280000000",
            "    }",
            "  },",
            "  \"chunks\" : [ {",
            "    \"offset\" : 0,",
            "    \"values\" : [ 1.0, 2.0 ]",
            "  } ]",
            "}, {",
            "  \"name\" : \"ts_calc_min\",",
            "  \"expr\" : {",
            "    \"binaryMin\" : {",
            "      \"timeSeriesName\" : \"foo\"",
            "    }",
            "  }",
            "} ]");
        e0 = assertThrows(TimeSeriesException.class, () -> TimeSeries.parseJson(jsonOneParam));
        assertEquals("Invalid binary min/max node calc JSON", e0.getMessage());

        // One parameter is missing
        final String jsonThreeParam = String.join(System.lineSeparator(),
            "[ {",
            "  \"metadata\" : {",
            "    \"name\" : \"ts\",",
            "    \"dataType\" : \"DOUBLE\",",
            "    \"tags\" : [ ],",
            "    \"regularIndex\" : {",
            "      \"startTime\" : 1420070400000,",
            "      \"endTime\" : 1437350400000,",
            "      \"spacing\" : 17280000000",
            "    }",
            "  },",
            "  \"chunks\" : [ {",
            "    \"offset\" : 0,",
            "    \"values\" : [ 1.0, 2.0 ]",
            "  } ]",
            "}, {",
            "  \"name\" : \"ts_calc_min\",",
            "  \"expr\" : {",
            "    \"binaryMin\" : {",
            "      \"timeSeriesName\" : \"ts\",",
            "      \"timeSeriesName\" : \"foo\",",
            "      \"timeSeriesName\" : \"bar\"",
            "    }",
            "  }",
            "} ]");
        e0 = assertThrows(TimeSeriesException.class, () -> TimeSeries.parseJson(jsonThreeParam));
        assertEquals("2 operands expected for a binary min/max comparison", e0.getMessage());

        // One parameter is missing
        final String jsonValueNull = String.join(System.lineSeparator(),
            "[ {",
            "  \"metadata\" : {",
            "    \"name\" : \"ts\",",
            "    \"dataType\" : \"DOUBLE\",",
            "    \"tags\" : [ ],",
            "    \"regularIndex\" : {",
            "      \"startTime\" : 1420070400000,",
            "      \"endTime\" : 1437350400000,",
            "      \"spacing\" : 17280000000",
            "    }",
            "  },",
            "  \"chunks\" : [ {",
            "    \"offset\" : 0,",
            "    \"values\" : [ 1.0, 2.0 ]",
            "  } ]",
            "}, {",
            "  \"name\" : \"ts_calc_min\",",
            "  \"expr\" : {",
            "    \"binaryMin\" : null",
            "  }",
            "} ]");
        e0 = assertThrows(TimeSeriesException.class, () -> TimeSeries.parseJson(jsonValueNull));
        assertEquals("Unexpected JSON token: VALUE_NULL", e0.getMessage());
    }

    @Test
    void jsonErrorMinMaxTests() {

        // Initialisation
        TimeSeriesException e0;

        // Both parameters (left, right) are missing
        final String jsonNoParam = String.join(System.lineSeparator(),
            "[ {",
            "  \"metadata\" : {",
            "    \"name\" : \"ts\",",
            "    \"dataType\" : \"DOUBLE\",",
            "    \"tags\" : [ ],",
            "    \"regularIndex\" : {",
            "      \"startTime\" : 1420070400000,",
            "      \"endTime\" : 1437350400000,",
            "      \"spacing\" : 17280000000",
            "    }",
            "  },",
            "  \"chunks\" : [ {",
            "    \"offset\" : 0,",
            "    \"values\" : [ 1.0, 2.0 ]",
            "  } ]",
            "}, {",
            "  \"name\" : \"ts_calc_min\",",
            "  \"expr\" : {",
            "    \"min\" : {",
            "    }",
            "  }",
            "} ]");
        e0 = assertThrows(TimeSeriesException.class, () -> TimeSeries.parseJson(jsonNoParam));
        assertEquals("Invalid min/max node calc JSON", e0.getMessage());

        // One parameter is missing
        final String jsonOneParam = String.join(System.lineSeparator(),
            "[ {",
            "  \"metadata\" : {",
            "    \"name\" : \"ts\",",
            "    \"dataType\" : \"DOUBLE\",",
            "    \"tags\" : [ ],",
            "    \"regularIndex\" : {",
            "      \"startTime\" : 1420070400000,",
            "      \"endTime\" : 1437350400000,",
            "      \"spacing\" : 17280000000",
            "    }",
            "  },",
            "  \"chunks\" : [ {",
            "    \"offset\" : 0,",
            "    \"values\" : [ 1.0, 2.0 ]",
            "  } ]",
            "}, {",
            "  \"name\" : \"ts_calc_min\",",
            "  \"expr\" : {",
            "    \"min\" : {",
            "      \"timeSeriesName\" : \"foo\"",
            "    }",
            "  }",
            "} ]");
        e0 = assertThrows(TimeSeriesException.class, () -> TimeSeries.parseJson(jsonOneParam));
        assertEquals("Invalid min/max node calc JSON", e0.getMessage());

        // One parameter is missing
        final String jsonThreeParam = String.join(System.lineSeparator(),
            "[ {",
            "  \"metadata\" : {",
            "    \"name\" : \"ts\",",
            "    \"dataType\" : \"DOUBLE\",",
            "    \"tags\" : [ ],",
            "    \"regularIndex\" : {",
            "      \"startTime\" : 1420070400000,",
            "      \"endTime\" : 1437350400000,",
            "      \"spacing\" : 17280000000",
            "    }",
            "  },",
            "  \"chunks\" : [ {",
            "    \"offset\" : 0,",
            "    \"values\" : [ 1.0, 2.0 ]",
            "  } ]",
            "}, {",
            "  \"name\" : \"ts_calc_min\",",
            "  \"expr\" : {",
            "    \"min\" : {",
            "      \"timeSeriesName\" : \"ts\",",
            "      \"integer\" : 1,",
            "      \"timeSeriesName\" : \"bar\"",
            "    }",
            "  }",
            "} ]");
        e0 = assertThrows(TimeSeriesException.class, () -> TimeSeries.parseJson(jsonThreeParam));
        assertEquals("Only 1 operand expected for a min/max", e0.getMessage());

        // One parameter is missing
        final String jsonValueNull = String.join(System.lineSeparator(),
            "[ {",
            "  \"metadata\" : {",
            "    \"name\" : \"ts\",",
            "    \"dataType\" : \"DOUBLE\",",
            "    \"tags\" : [ ],",
            "    \"regularIndex\" : {",
            "      \"startTime\" : 1420070400000,",
            "      \"endTime\" : 1437350400000,",
            "      \"spacing\" : 17280000000",
            "    }",
            "  },",
            "  \"chunks\" : [ {",
            "    \"offset\" : 0,",
            "    \"values\" : [ 1.0, 2.0 ]",
            "  } ]",
            "}, {",
            "  \"name\" : \"ts_calc_min\",",
            "  \"expr\" : {",
            "    \"min\" : null",
            "  }",
            "} ]");
        e0 = assertThrows(TimeSeriesException.class, () -> TimeSeries.parseJson(jsonValueNull));
        assertEquals("Unexpected JSON token: VALUE_NULL", e0.getMessage());
    }
}