Graf Files and Time Series

Time Series

Some attributes in a Study represent a time series indexed by another attribute. Here we will be setting a time series for the attribute EmissionCost, from PSRGasEmission, which is indexed by DateEmissionCost.

First we create a Dict with EmissionCost and DateEmissionCost data.

using PSRClassesInterface
PSRI = PSRClassesInterface
using Dates

series = Dict{String,Vector}(
    "DateEmissionCost" => [
        Dates.Date("1900-01-01"),
        Dates.Date("2013-01-01"),
        Dates.Date("2013-02-01")
    ],
    "EmissionCost" => [0.0,3.0,3.0]
    )
Dict{String, Vector} with 2 entries:
  "DateEmissionCost" => [Date("1900-01-01"), Date("2013-01-01"), Date("2013-02-…
  "EmissionCost"     => [0.0, 3.0, 3.0]

Then, we save the time series to the study using the function PSRClassesInterface.set_series!. Notice that when we are saving this time series, we are specifying the element from the collection that has this series, using its index.

temp_path = joinpath(tempdir(), "PSRI")

data = PSRI.create_study(PSRI.OpenInterface(), data_path = temp_path)

PSRI.create_element!(data, "PSRGasEmission")

PSRI.set_series!(
    data, 
    "PSRGasEmission", 
    "DateEmissionCost",
    1, # element index in collection
    series
    )

Using a SeriesTable

We can later retrieve the series for the element in the collection with PSRClassesInterface.get_series, which will return a SeriesTable object. It can be later displayed as a table in your terminal.

When using this function, we need the collection, the element index and the attribute that indexes the elements.

using DataFrames
series_table = PSRI.get_series(
    data, 
    "PSRGasEmission", 
    "DateEmissionCost", 
    1 # element index in collection
    )

DataFrame(series_table)

Graf files

The data relative to a Study is usually stored in a JSON file, where an attribute can have its data indexed by time intervals, as presented earlier.

However, when a time series attribute is multidimensional, it can be too large to be stored in a JSON. For these cases, we save the data in a separate file. We will refer to such file as Graf file. When an attribute has its information in a Graf file, there's an entry in the regular JSON file specifying it.

In the following example, each PSRDemand object will have its attribute Duracao data associated with a time series, saved in the files duracao.hdr and duracao.bin. Objects are distinguished by the parmid attribute, which in this case has the AVId value of each PSRDemand element.

A Graf file stores the values of an attribute for every element in a collection. So for this example, all values for the attribute Duracao will be store in the Graf file.

"PSRGasEmission": [
    {
        "AVId": "Agent 1"
        "name": "psr_name"
    },
    {
        "AVId": "Agent 2"
        "name": "psr_name2"
    },
    {
        "AVId": "Agent 3"
        "name": "psr_name3"
    }
],
"GrafScenarios": [
    {
        "classname": "PSRDemand",
        "parmid": "AVId",
        "vector": "Duracao",
        "binary": [ "duracao.hdr", "duracao.bin" ]
    }
]

Graf file format

A Graf file composed of a header and a table with the following elements:

  • Stage
  • Scenario
  • Block
  • Agents (one entry for each agent)

Each agent represents an element from the collection the graf file is linked to.

Visual example of a graf file

Using the previous example with PSRDemand objects, the Duracao for each object will be displayed in the Agents columns, that will take the name of the AVId attribute, resulting on the following:

StgSceBlockAgent 1Agent 2Agent 3
1111.05.010.0
1121.56.511.5
1132.07.012.0
..................

Graf Header

A Graf file header contains data about the time series contents. Some important information are whether the time series varies per block and/or per Scenario, the number of agents, the unit of measurement and on which stage it should start.

Writing a time series into a Graf file

In this example we will demonstrate how to save a time series into a csv or binary file. First, we create a random array which has the data for a time series:

import PSRClassesInterface
const PSRI = PSRClassesInterface

#Creates dummy data
n_blocks = 3
n_scenarios = 1
n_stages = 1
n_agents = 1

time_series_data = rand(Float64, n_agents, n_blocks, n_scenarios, n_stages)

nothing #hide

There are two ways of saving the data to a file, save the data in the file directly or iteratively. To save the data directly use the function PSRClassesInterface.array_to_file by calling:

FILE_PATH = joinpath(tempdir(), "example")

PSRI.array_to_file(
    PSRI.OpenBinary.Writer,
    FILE_PATH,
    time_series_data,
    agents = ["Agent 1", "Agent 2", "Agent 3", "Agent 4", "Agent 5"],
    unit = "H";
    initial_stage = 3,
    initial_year = 2006,
)

To save the data iteractively use the function PSRClassesInterface.open to create an PSRClassesInterface.AbstractWriter. Save the data of each registry to the file using the function PSRClassesInterface.write_registry and then close the data stream calling the function PSRClassesInterface.close.

iow = PSRI.open(
    PSRI.OpenBinary.Writer,
    FILE_PATH,
    blocks = n_blocks,
    scenarios = n_scenarios,
    stages = n_stages,
    agents = ["Agent 1", "Agent 2", "Agent 3", "Agent 4", "Agent 5"],
    unit = "H",
    initial_stage = 1,
    initial_year = 2006,
)

for stage = 1:n_stages, scenario = 1:n_scenarios, block = 1:n_blocks
    PSRI.write_registry(
        iow,
        time_series_data[:, block, scenario, stage],
        stage,
        scenario,
        block
    )
end

PSRI.close(iow)

Reading a time series from a graf file

A similar logic can be used to read the data from a file. You can read it directly or iteratively. To read the data directly use the function PSRClassesInterface.file_to_array or PSRClassesInterface.file_to_array_and_header

data_from_file = PSRI.file_to_array(
        PSRI.OpenBinary.Reader, 
        FILE_PATH;
        use_header=false
    )

@assert all(isapprox.(data_from_file, time_series_data, atol=1E-7))

data_from_file_and_header, header = PSRI.file_to_array_and_header(
        PSRI.OpenBinary.Reader, 
        FILE_PATH;
        use_header=false
    )
@assert all(isapprox.(data_from_file_and_header, time_series_data, atol=1E-7))

To choose the agents order set use_header to true and label the agents in header.

data_from_file = PSRI.file_to_array(
        PSRI.OpenBinary.Reader, 
        FILE_PATH;
        use_header=true,
        header=["Agent 5", "Agent 2", "Agent 3", "Agent 4", "Agent 1"]
    )
@assert all(isapprox.(data_from_file[1, :, :, :], time_series_data[end, :, :, :], atol=1E-7))
@assert all(isapprox.(data_from_file[end, :, :, :], time_series_data[1, :, :, :], atol=1E-7))

To read the data iteractively use the function PSRClassesInterface.open to create an PSRClassesInterface.AbstractReader and read each registry iteratively. At the end you should close the PSRClassesInterface.AbstractReader by calling PSRClassesInterface.close

ior = PSRI.open(
    PSRI.OpenBinary.Reader, 
    FILE_PATH;
    use_header = false
)

data_from_file = zeros(n_agents, n_blocks, n_scenarios, n_stages)

for stage = 1:n_stages, scenario = 1:n_scenarios, block = 1:n_blocks
    data_from_file[:, block, scenario, stage] = ior.data
    PSRI.next_registry(ior)
end

PSRI.close(ior)

Using Graf files in a study

As presented earlier, an attribute for a collection can have its data stored in a Graf file, all that being specified in the GrafScenarios entry of the study JSON.

If you have a Graf file that should be linked to a study, you can use the function PSRClassesInterface.link_series_to_file to do so.

PSRI.create_element!(data, "PSRDemand", "AVId" => "Agent 1")
PSRI.create_element!(data, "PSRDemand", "AVId" => "Agent 2")
PSRI.create_element!(data, "PSRDemand", "AVId" => "Agent 3")
PSRI.create_element!(data, "PSRDemand", "AVId" => "Agent 4")
PSRI.create_element!(data, "PSRDemand", "AVId" => "Agent 5")

PSRI.link_series_to_file(
        data, 
        "PSRDemand", 
        "Duracao", 
        "AVId",
        FILE_PATH
    )

Using a GrafTable

We can retrieve the data stored in a Graf file using the PSRClassesInterface.get_graf_series function. This function returns a GrafTable object.

When using this function, we need the collection and its attribute that is linked to a Graf file.

graf_table = PSRI.get_graf_series(
        data,
        "PSRDemand",
        "Duracao";
        use_header = false
    )

Once you have a GrafTable object, you can display it as a table in your terminal

using DataFrames

DataFrame(graf_table)

Vector from graf file

You can get a vector that corresponds to a row in a Graf file with the values for the agents correspoding to the current stage, scenario and block.

For that, we will have to use the function PSRClassesInterface.mapped_vector.

vec = PSRI.mapped_vector(
        data, 
        "PSRDemand", 
        "Duracao",
        Float64
    )

The parameters that were used to retrieve the row value in the Graf table can be changed with the following functions:

These methods don't automatically update the vector. For that, we use the function PSRClassesInterface.update_vectors!, which update all vectors from our Study.

PSRI.update_vectors!(data)

However, it might be interesting to update only one or a group of vectors. To be able to do that, we will have to set a filter tag when we create them.

vec2 = PSRI.mapped_vector(
        data, 
        "PSRDemand", 
        "Duracao",
        Float64,
        filters = ["test_filter"]
    )

Then, when we run:

PSRI.update_vectors!(data, "test_filter")

Comparison between a GrafTable and a SeriesTable

In this section we have introduced some new concepts about table-like types. That being said, let's review the main differences between GrafTables and SeriesTables:

  • SeriesTable
    • Is linked to a single element from a collection
    • It can have time series of different attributes that are indexed to a single attribute (all belonging to the same element)
  • GrafTable
    • Is linked to the whole collection. So for an attribute, every element in the collection will have an entry in the graf file
    • It has the time series for only one attribute