Friends

Detailed Description

Manages value interdependencies for a particular value or set of values in a Context.

A DependencyTracker ("tracker" for short) provides notifications of changes to the managed value to downstream subscribers, and may invalidate an associated cache entry. The "managed value" can be a source like time or state, or a cached computation. A particular tracker is selected using a DependencyTicket ("ticket") which provides very fast access to the tracker. The ticket is used by both the System and Context as a way to identify dependencies, while trackers exist only in the Context.

Each DependencyTracker manages dependencies for a value, or group of related values, upon which some downstream computations may depend, and maintains lists of downstream dependents (subscribers) and upstream prerequisites. An optional CacheEntryValue may be registered with a tracker in which case the tracker will mark the cache value out of date when one of its prerequisites has changed.

A single DependencyTracker can represent interdependencies within its subcontext, and to and from other subcontexts within the same containing Context tree. Trackers are always owned by a DependencyGraph that is part of a particular subcontext, and should always be created through methods of DependencyGraph; don't construct them directly yourself.

DependencyTracker objects within a Context are nodes in a directed acylic graph formed by "is-prerequisite-of" edges leading from source values (like time, state, parameters, and input ports) to dependent cached computations and output ports. A DependencyTracker maintains lists of both its downstream subscribers and its upstream prerequisites. The entries in both lists are pointers to other DependencyTrackers. That requires special handling when cloning a Context, since the internal pointers to the DependencyTracker objects in the source must be replaced by their corresponding pointers in the copy.

DependencyTrackers may simply group upstream values, without representing a new value or computation. For example, the three continuous state subgroups q, v, and z are each associated with their own DependencyTracker. There is also a tracker that monitors changes to any variable within the entire collection of continuous variables xc≜{q,v,z}; that tracker subscribes to the three individual trackers. Similarly, individual discrete variable groups dᵢ collectively determine the discrete state xd≜{dᵢ}, individual abstract state variables aᵢ determine the abstract state xa≜{aᵢ}, and the full state is x≜{xc,xd,xa}. Here is a graph showing time and state trackers and some hypothetical cache entry trackers.

The parenthesized nodes are DependencyTrackers for the indicated values, and a directed edge (a)->(b) can be read as "a is-prerequisite-of b" or "a determines b". The graph also maintains reverse-direction edges (not shown). A reversed edge (a)<-(b) could be read as "b subscribes-to a" or "b depends-on a".)

These grouped trackers simplify dependency specification for quantities that depend on many sources, which is very common. For example, they allow a user to express a dependence on "all the inputs" without actually having to know how many inputs there are, which might change over time. Grouped trackers also serve to reduce the total number of edges in the dependency graph, providing faster invalidation. For example, if there are 10 computations dependent on q, v, and z (which frequently change together) we would have 30 edges. Introducing (xc) reduces that to 13 edges.

Downstream computations may subscribe to any of the individual or grouped nodes.

Notifies this DependencyTracker that its managed value was directly modified or made available for mutable access.

That is, this is the initiating event of a value modification. All of our downstream subscribers are notified but the associated cache entry (if any) is not invalidated (see below for why). A unique, positive change_event should have been obtained from the owning Context and supplied here.

Why don't we invalidate the cache entry? Recall that this method is for initiating a change event, meaning that the quantity that this tracker tracks is initiating an invalidation sweep, as opposed to just reacting to prerequisite changes. Normally cache entries become invalid because their prerequisites change; they are not usually the first step in an invalidation sweep. So it is unusual for NoteValueChange() to be called on a cache entry's dependency tracker. But if it is called, that is likely to mean the cache entry was just given a new value, and is therefore valid; invalidating it now would be an error.

Throws an std::logic_error if there is something clearly wrong with this DependencyTracker object.

If the owning subcontext is known, provide a pointer to it here and we'll check that this tracker agrees. If you know which cache entry is supposed to be associated with this tracker, supply a pointer to that and we'll check it (trackers that are not associated with a real cache entry are still associated with the CacheEntryValue::dummy()). In addition we check for other internal inconsistencies.

Exceptions

std::logic_error

for anything that goes wrong, with an appropriate explanatory message.