Draft Specification for Temporally Extended Attributes
Compiled by Carter Butts, w/extensive input from Skye Bender-deMoll, Martina Morris, Pavel Krivitsky, and others
Definition: A "temporally extended attribute" (TEA) is a standard edge, vertex, or network attribute that (1) has a name ending in ".active" and (2) carries meta-data regarding its state over time.
- TEAs must be compatible with the existing network class specification.
- All operations involving TEAs must be implementable within the existing network class API.
- TEAs must allow for the same time/state semantics as other elements of the networkDynamic extended class.
- implement onset,terminus, at, and length alternatives for specifying spells and queires
- modify network arguments inplace
- set networkDynamic class on argument if not networkDynamic
- TEA methods should allow the following operations:
- Cross-section queries (selection of attributes at an instant in time).
- Return types must include attribute values or activity tables.
- Inactive attributes should return NA.
- Interval queries (all values taken by selected active attribute(s) over an arbitrary continuous interval).
- As above, return values must include attribute values or activity table information.
- Next event/previous event queries (find next or previous state change given query time).
- Return NA if no event.
- Should be able to return transitional state, time, or both.
- "apply" function semantics for interval or cross-sectional queries.
- Activity table queries (i.e., return spell list), using (a) or (b) query semantics.
- List TEA attributes.
- Cross-section queries (selection of attributes at an instant in time).
- TEA methods should exhibit reasonable default behavior when called against non-TEA attributes:
- During attribute seek ("get"), failure to find *.active should default to a seek for * (unless selected otherwise).
- Non-TEA attributes found by failover seek should be treated as universally active/inactive in accord with is.active semantics (see man page for is.active).
- TEA local activity is independent of the entity to which it is attached.
- Where feasible, however, functions should allow optional "filtering" behavior by activity of the underlying entity.
Acceptable Limitations (?):
- The current networkDynamic implementation is subject to R vector allocation limits, and as such can record on the order of ten million spells per entity on a 64-bit machine. It is assumed that this is acceptable (otherwise, implementation becomes substantially more difficult).
- Creation/deletion of spells can be quadratic in number of spells on the entity in question.
- Standard network class overhead (e.g., linear search cost in number of attributes per object) is acceptable.
- Although non-extended network class methods must be able to handle TEAs without producing errors, they need not return results that are immediately useful (w/out post-processing).
- Activity consistency across elements is not dynamically verified. (I.e., it is up to the user to ensure that an inactive vertex doesn't have an active attribute.)
A temporally extended attribute is an edge, vertex, or network attribute satisfying the following properties:
- Its name consists of an arbitrary prefix, together with the suffix ".active".
- Its value consists of a two-element list, whose respective contents must be maintained in order as follows:
- A list of value entries, such that the ith list element is the value of the attribute for the ith spell in the associated activity matrix. An attribute not active at any given point in time is defined as having a value of NA (but the reverse is not true -- an active attribute can also take a value of NA).
- A two-column numeric matrix, conforming to the specifications for activity attributes in the networkDynamic package. Every active spell indicated in the activity matrix must correspond to exactly one entry of the associated value list, and these must be maintained in order (i.e., the value of the attribute for the ith active spell is the ith element in the value list).
Since TEAs are standard network attributes, they can be manipulated directly via the existing network attribute methods (either via R or the C-level API). Likewise, existing summary and other methods in the base class will simply interpret TEAs as they would any other list-valued attributes. Thus, TEA-extended network objects are backward compatible (as required).
Extended Attribute Methods
Several special-purpose methods may be created to facilitate interaction with TEAs. The following are initial proposals of this type (briefly described):
The activate.*.attribute methods act as a cross between the new activate.* and the old set.*.attribute methods. They are used to activate an attribute for a given spell, and in so doing set a value for that spell. The corresponding deactivate methods are more straightforward, deactivating the attribute over a set interval (and removing any spells/values as needed).
activate.edge.attribute(x, prefix, value, onset=NULL, terminus=NULL,length=NULL, at=NULL, e=1:length(x$mel), dynamic.only=FALSE) activate.edge.value(x, prefix, value, onset=NULL, terminus=NULL,length=NULL, at=NULL, e=1:length(x$mel), dynamic.only=FALSE) activate.network.attribute(x, prefix, value, onset=NULL, terminus=NULL,length=NULL, at=NULL, dynamic.only=FALSE) activate.vertex.attribute(x, prefix, value, onset=NULL, terminus=NULL,length=NULL, at=NULL, v=1:network.size(x), dynamic.only=FALSE) deactivate.edge.attribute(x, prefix, onset=NULL, terminus=NULL,length=NULL, at=NULL, e=1:length(x$mel), dynamic.only=FALSE) deactivate.edge.value(x, prefix, onset=NULL, terminus=NULL,length=NULL, at=NULL, e=1:length(x$mel), dynamic.only=FALSE) deactivate.network.attribute(x, prefix, onset=NULL, terminus=NULL,length=NULL, at=NULL, dynamic.only=FALSE) deactivate.vertex.attribute(x, prefix, onset=NULL, terminus=NULL,length=NULL, at=NULL, v=1:network.size(x), dynamic.only=FALSE)
Behavior sketch: For the activate methods, query the selected entities for a TEA with the specified prefix. If no such TEA exists and dynamic.only==FALSE, perform a secondary seek with an attribute such that attrname==prefix; if such an attribute is found, convert it to a TEA, and otherwise (or if no such TEA exists and dynamic.only==TRUE) create a TEA with the appropriate prefix. Once the appropriate TEA is found/created, modify the TEA's spell table to make the attribute active for the specified period, and set the value of the corresponding spell to the contents of the value argument. (This process is repeated for every element whose attribute is set in this fashion, with semantics corresponding to set.*.attribute.) Multiple spells can be set for each vertex or edge by including extra values in the time specification (onset, at, etc..) and repeating values in the e or v argument to indicate which element the spells should be added to.
For the deactivate methods, query the selected entities for a TEA with the specified prefix; if no such TEA exists and dynamic.only=FALSE, perform a secondary seek for an attribute with attrname==prefix. If no acceptable attribute is found, exit. Otherwise, modify the spell table of the attribute in question to force deactivation over the specified interval (converting the attribute to a TEA if necessary), adjusting the associated value list as necessary.
The get.*.attribute.active methods function much like the corresponding conventional functions, but filter information in various ways based on the activity of the underlying attributes. Usage is as follows:
get.edge.attribute.active(el, prefix, onset=NULL, terminus=NULL,length=NULL, at=NULL, rule = c("any", "all"), active.default = TRUE, dynamic.only=FALSE, require.active=FALSE,return.tea=FALSE,unlist=TRUE) get.edge.value.active(x, prefix, onset=NULL, terminus=NULL,length=NULL, at=NULL, rule = c("any", "all"), active.default = TRUE, dynamic.only=FALSE, require.active=FALSE,return.tea=FALSE,unlist=TRUE) get.network.attribute.active(x, prefix, onset=NULL, terminus=NULL,length=NULL, at=NULL, rule = c("any", "all"), active.default = TRUE, dynamic.only=FALSE, require.active=FALSE,return.tea=FALSE,unlist=TRUE) get.vertex.attribute.active(x, prefix, onset=NULL, terminus=NULL,length=NULL, at=NULL, rule = c("any", "all"), na.omit = FALSE, null.na = TRUE, active.default = TRUE, dynamic.only=FALSE, require.active=FALSE,return.tea=FALSE,unlist=TRUE)
Behavior sketch: For the entities in question, seek a TEA with the indicated name prefix (failing over to regular attributes with attrname==prefix if dynamic.only=FALSE). Given such a TEA, return the dynamic value information corresponding to the query interval (with all query parameters interpreted as per is.active). By default, the activity of the underlying entity is ignored; if require.active==TRUE, attributes are considered inactive when the entity to which they are attached is inactive (w/active.default being used where necessary to impute activity). If return.tea=TRUE values are in TEA form, specifically a list containing a value list along with a spell list (the subset of the original that matches the query). Otherwise return single matching value if one found, otherwise return earliest value and throw WARNING that multiple values match for the query spell.
The list.*.attribute.active methods act like their normal counterparts, but implement basic temporal query logic on top of their standard functionality. Usage is as follows:
list.network.attributes.active(x, onset=NULL, terminus=NULL,length=NULL, at=NULL, rule = c("any", "all"), active.default = TRUE, dynamic.only=FALSE, require.active=FALSE) list.edge.attributes.active(x, onset=NULL, terminus=NULL,length=NULL, at=NULL, rule = c("any", "all"), active.default = TRUE, dynamic.only=FALSE, require.active=FALSE) list.vertex.attributes.active(x, onset=NULL, terminus=NULL,length=NULL, at=NULL, rule = c("any", "all"), active.default = TRUE, dynamic.only=FALSE, require.active=FALSE)
Behavior sketch: For the entity type in question, search the (onset,terminus) interval using the specified query rule for all active attributes, returning their names. Query arguments correspond to is.active. If dynamic.only==TRUE, only TEAs are considered; otherwise, non-TEA attributes are also employed (with activity as defined by active.default). By default, no check is made to ensure that the underlying entity is active during the query interval; if require.active==TRUE, then, attributes are regarded as inactive when the entities to which they are attached are inactive (using active.default to determine this as needed).
In addition to extensions of standard methods, it may be useful to create more complex custom query methods for dynamic attributes. Among these, some generalization of the "apply" function seems most useful; however, I am not yet sure how best to do this without creating a long list of methods. This is an area that might be deferred until more experience with the basic functionality has been obtained.
Spec says get methods should return TEA value and spell list. Skye thinks we should have a parameter "return.tea=FALSE" which would return this. Otherwise return single matching value if one found, or the first (earliest) value matching query and throw Warning if multiple values match. I think this will cover the most common use cases better. ADDED TO SPEC, IMPLEMENTED
Since spell structures forces non-overlapping, should we add an "earliest" and "latest" rule, and if it is set don't throw warning for multiple TEA match within query spell? IMPLEMENTED
Since these function names are getting very long, should we have short-hand versions? activate.vertex.attribute() : vAttrOn()
What should behavior be if underlying vertex or edge is coded as "missing"? Always return NA ?
What should behavior be for activating attributes on edges that do not exist, or networks with no edge? throw error?
In a get method, if TEA attribute is present but not active at time point (or missing for that vertex or edge) and dynamic.only=FALSE, should it consider matching non-TEA? (Might return object with unexpected structure if return.tea=TRUE)
If we are returning a TEA structure, it doesn't make sense to "fall through" to non-TEA attributes, since they won't have TEA structure. Proposing to set dynamic.only=TRUE if return.tea=TRUE . IMPLEMENTED
Should we add "v" and "e" params to get methods to allow fetching from a subset of vertices and edges? Doesn't seem like it would add much complexity. Why should get.edge.attribute.active() take an edgelist as its argument instead of a network?
As the spec states, listing active attributes is more expensive that querying the attributes (because have to query all of them to determine what is on the list). Would it make more sense to have list* methods just return the .active attributes, and user can directly query the ones they are interested in? Spec also doesn't say if should return "test.active" or "test" for active attributes.
The 'fall through' behavior of the get.*.active methods gives surprising results if you accidentally query for "test.active" instead of "test". Since it doesn't find "test.active.active", it uses the non-tea query, which then returns the full tea structure.
For activation methods, the behavior of when including multiple onsets or terminus values are not defined in this spec. Man pages state that multiple onsets/termini should correspond to multiple elements in values list. Implementation is inconsistent.
We have a require.active argument for get methods that checks the underlying element activity. Seems like it would be (perhaps more?) useful in the set methods?