Improve annotation core and add basic analysis annotators.
parent
7cfa7ea2bf
commit
51fdef55bd
@ -0,0 +1,114 @@
|
|||||||
|
from . import Annotator, Annotation
|
||||||
|
from ..topo_v3 import MetricType
|
||||||
|
import heapq
|
||||||
|
from enum import Enum
|
||||||
|
from functools import total_ordering
|
||||||
|
|
||||||
|
class TopologyDifference(Annotator):
|
||||||
|
"""Finds differences between ancestors.
|
||||||
|
|
||||||
|
Currently, we only support the "reference vs. current" comparison, since
|
||||||
|
that is the most useful case and it is clear which vertices are new and
|
||||||
|
which old."""
|
||||||
|
class Status(Enum):
|
||||||
|
Missing = 'missing'
|
||||||
|
New = 'new'
|
||||||
|
Discrepant = 'discrepant'
|
||||||
|
|
||||||
|
def __init__(self, _param):
|
||||||
|
self.old_label = 'reference'
|
||||||
|
self.new_label = 'current'
|
||||||
|
def annotate(self, atopo):
|
||||||
|
result = Annotation()
|
||||||
|
old = atopo.topology.ancestors[self.old_label]
|
||||||
|
new = atopo.topology.ancestors[self.new_label]
|
||||||
|
|
||||||
|
# Vertices:
|
||||||
|
oldv = set(old.vertices.keys())
|
||||||
|
newv = set(new.vertices.keys())
|
||||||
|
# TODO: Maybe match vertices?
|
||||||
|
only_old = oldv - newv
|
||||||
|
only_new = newv - oldv
|
||||||
|
common = oldv & newv
|
||||||
|
discrepant = set()
|
||||||
|
for vtxid in common:
|
||||||
|
o = old.vertices[vtxid]
|
||||||
|
n = new.vertices[vtxid]
|
||||||
|
# Only field that can differ is type, assuming consistent topology
|
||||||
|
if o.type != n.type:
|
||||||
|
discrepant.add(vtxid)
|
||||||
|
for v in only_old: result.for_vertex[v] = self.Status.Missing
|
||||||
|
for v in only_new: result.for_vertex[v] = self.Status.New
|
||||||
|
for v in discrepant: result.for_vertex[v] = self.Status.Discrepant
|
||||||
|
|
||||||
|
# Edges:
|
||||||
|
olde = old.edges
|
||||||
|
newe = new.edges
|
||||||
|
only_old = olde - newe
|
||||||
|
only_new = newe - olde
|
||||||
|
common = olde & newe
|
||||||
|
discrepant = set()
|
||||||
|
for edge in common:
|
||||||
|
o = old.edges[edge]
|
||||||
|
n = new.edges[edge]
|
||||||
|
if o != n: discrepant.add(edge)
|
||||||
|
for e in only_old: result.for_edge[e] = self.Status.Missing
|
||||||
|
for e in only_new: result.for_edge[e] = self.Status.New
|
||||||
|
for e in discrepant: result.for_edge[e] = self.Status.Discrepant
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
class ShortestPathTree(Annotator):
|
||||||
|
"""Annotates the shortest path tree edges with True.
|
||||||
|
|
||||||
|
Takes a tuple of the starting vertex and topology ancestor identifier as
|
||||||
|
the parameter (None for the whole topology).
|
||||||
|
|
||||||
|
Since we have computed the distances of individual vertices, we annotate
|
||||||
|
the vertices too. The annotations are of form (metric_type, distance)
|
||||||
|
|
||||||
|
If the start vertex is not found, annotates the whole topology with None."""
|
||||||
|
def __init__(self, param):
|
||||||
|
vertex, ancestor = param
|
||||||
|
self.start_vtxid = vertex
|
||||||
|
self.ancestor = ancestor
|
||||||
|
def annotate(self, atopo):
|
||||||
|
result = Annotation()
|
||||||
|
topo = atopo.topology.ancestors[self.ancestor]
|
||||||
|
if self.start_vtxid not in topo.vertices:
|
||||||
|
result.for_topology = None
|
||||||
|
return result
|
||||||
|
# We need a simple wrapper around the edges, so they are comparable
|
||||||
|
@total_ordering
|
||||||
|
class CE:
|
||||||
|
def __init__(self, distance, edge):
|
||||||
|
self.mt = edge.metric_type
|
||||||
|
self.dist = distance
|
||||||
|
self.e = edge
|
||||||
|
def __lt__(self, other):
|
||||||
|
return (self.mt, self.dist) < (other.mt, other.dist)
|
||||||
|
def __ew__(self, other):
|
||||||
|
return (self.mt, self.dist) == (other.mt, other.dist)
|
||||||
|
heap = [CE(e.cost, e) for e in topo.vertices[self.start_vtxid].outgoing_edges]
|
||||||
|
heap.sort()
|
||||||
|
# We run a bit modified Dijkstra algorithm, since we want to find a DAG
|
||||||
|
# of shortest paths, not a tree. The heap contains edges together with
|
||||||
|
# target's distances and we add all the edges that see the specific
|
||||||
|
# target with the same distance.
|
||||||
|
distances = {
|
||||||
|
self.start_vtxid: (MetricType.Type1, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
while len(heap) > 0:
|
||||||
|
ce = heapq.heappop(heap)
|
||||||
|
if ce.e.target not in distances or distances[ce.e.target] == (ce.mt, ce.dist):
|
||||||
|
result.for_edge[ce.e] = True
|
||||||
|
distances[ce.e.target] = (ce.mt, ce.dist)
|
||||||
|
for oe in topo.vertices[ce.e.target].outgoing_edges:
|
||||||
|
# Type 2 metrics are external only, so if ce.e.mt == 2,
|
||||||
|
# this point is not reached (ce.e.target has no outgoing edges)
|
||||||
|
assert ce.mt == 1
|
||||||
|
new_dist = ce.dist + oe.cost if ce.mt == oe.metric_type else oe.cost
|
||||||
|
heapq.heappush(heap, CE(new_dist, oe))
|
||||||
|
result.for_vertex = distances
|
||||||
|
return result
|
Loading…
Reference in New Issue