Scripting with time series

Technical Note #57
March 2021

By Tessa Hayman

Tessa Hayman guides you through the complexity of handling time series via Python scripting and explains how to create your custom time series in a way that can be visualized in the UI.

What is a time series?

A time series is a set of data related to an object, and this data changes over time. For instance the count on a section would be stored in a time series with values for each detection interval, say 15 minutes.

In Aimsun Next, you can view a time series in the time series tab of an object. For example for a replication:

A time series is an object of the class GKTimeSerie and it is stored in an attribute, a GKColumn, of the object it refers to. You can read the standard deviation, Max, Min, Mean and RMS from the GKTimeSerie object. 

 

Each GKTimeSerie has an associated description object (GKTSDescription). This contains information about the duration, start and end time, interval of the time series it applies to. 

 

The GKTimeSerieIndex object is used to access a specific interval for a time series for which you want to read or write a value. To specify the 5th interval, you would use GKTimeSerieIndex(4) as the numbering starts at 0. 

 

How to read data from a time series

To get a time series from an object you can call the method getDataValueTS(), passing as argument the GKColumn that stores it.

 

If you want to get directly the value for an interval from a time series of an object, you can call the method getDataValueInTS() instead, passing as argument the GKTimeSerieIndex in addition to the GKColumn that stores the time series.

 

Note that getDataValueInTS() returns a tuple, in which the first item is the value of the time series and the second is the deviation.

 

To find a column name, you can look in the types window after you have either retrieved a database or run the model. For instance here are the outputs for GKSection.

 

The name is made up of:

 

  • STATIC or DYNAMIC
  • Object type
  • Output name
  • Replication ID
  • Vehicle type ID (or 0 for all vehicles)
  • Lane number (or 0 for all lanes)

For example, to read the density on a lane of a section during an interval of a replication, the following function could be used. Note that the 1st interval is labelled 0, the second is labelled 1 and so on.

				
					def readdensity( section, interval, replicationid, lanenumber, vehicleid=0 ):
column = model.getColumn( "DYNAMIC::GKSection_density_"+str(replicationid)+"_"+str(vehicleid)+"_"+str(lanenumber) )
	(value, deviation) = section.getDataValueInTS( column, GKTimeSerieIndex(interval) )
	return value

				
			

How to write data to a time series

To create new time series, we have to:

 

  • Create the column that stores the time series associated to each object of a given type
  • Create a time series description (GKTSDescription), which defines, at least, the start time, end time, interval of the time series, and optionally also the null value and how to compute an aggregated value for the entire period
  • While looping through the objects, loop through the intervals and call the method setDataValueInTS() to fill the value for that interval

 

For example, the following script creates a time series for nodes in which it stores the cycle time of the control plan active at that time of the day. It goes through the period covered by the master control plan in 15 min intervals. You must assign it to the context menu of Master Control Plans and run it by right clicking on a master control plan.

				
					fromDate = QDateTime(QDate().currentDate(), target.initialTime())
toDate = fromDate.addSecs(target.duration().toSeconds()[0])
interval = GKTimeDuration(0, 15, 0)
fromDate = fromDate.addSecs(interval.toSeconds()[0])

type = model.getType("GKNode")
column = type.getColumn( "GKNode::cycle_%s" % target.getId(), GKType.eSearchOnlyThisType )
if column == None:
	column = type.addColumn( "GKNode::cycle_%s" % target.getId(), GK.BuildContents("Cycle Time", target), GKColumn._GKTimeSerie, GKColumn.eExternal )
column.setConversion(GK.eConversionUndefined, "", "")

tsDescription = GKTSDescription.getCreateDescription( column, "cycleTime" )
tsDescription.setAggregationType(GK.eAggregationUndefined)
tsDescription.setNullValue(-1)
tsDescription.setTime(fromDate, toDate, interval)

now = fromDate
while now <= toDate:
	fromTime = now.time().second()+now.time().minute()*60+now.time().hour()*3600
	for node in model.getCatalog().getObjectsByType(type).itervalues():
		controlJunction = target.getControlJunction(node.getId(), fromTime, fromTime+1)[0]
		if controlJunction != None:
			cycle = controlJunction.getCycle()
			node.setDataValueInTS(column, GKTimeSerieIndex(tsDescription.getInterval(now)), cycle, 0.0, tsDescription)
	now = now.addSecs(interval.toSeconds()[0])

				
			

Note that the column is created using GK.BuildContents, so that the originator, in this case the selected master control plan, is correctly set.

 

If the column and the time series description are correctly defined, the time series will appear in the dialogue of signalised nodes.

And if you run the script for multiple master control plans, you can use the time series plot to quickly spot the difference, for example between the cycle during the weekday and the cycle during the weekend.