def remove_old(self, time=None): """Removes old observations from the interpretation.""" if time is None: time = max(self.past_metrics.time, self.focus.earliest_time) - C.FORGET_TIMESPAN #A minimum number of observations is kept nmin = min(C.MIN_NOBS, len(self.observations)) if nmin > 0: time = max(self.past_metrics.time, min(time, self.observations[-nmin].lateend - 1)) dummy = EventObservable() dummy.end.set(time, time) nhyp = abst = abstime = 0.0 #Old observations are removed from all lists. for lstname in ('observations', 'abstracted', 'unintelligible'): lst = getattr(self, lstname) idx = lst.bisect_right(dummy) if (idx > 0 and self.parent is not None and getattr(self.parent, lstname) is lst): lst = lst.copy() setattr(self, lstname, lst) if lstname == 'observations': nhyp = idx elif lstname == 'abstracted': abstime = sum(o.earlyend - o.latestart + 1 for o in lst[:idx] if ap.get_obs_level(type(o)) == 0) abst = idx del lst[:idx] self.past_metrics = PastMetrics(time, self.past_metrics.abst + abst, self.past_metrics.abstime + abstime, self.past_metrics.nhyp + nhyp)
def plot_constraints_network(interpretation, with_labels=False, fig=None): """ Draws the full temporal constraints network of a specific interpretation. """ #List with all the temporal constraints of the network lst = [] for pat in interpretation.patterns: for tnet in pat.temporal_constraints: lst.extend(tnet.get_constraints()) G = nx.DiGraph() for const in lst: G.add_edge(const.va, const.vb) #Color assignment observations = interpretation.get_observations() colors = [] labels = {} pos = {} for node in G.nodes_iter(): for obs in observations: if node is obs.start or node is obs.end: colors.append(_get_obs_descriptor(obs)[0]) x = obs.earlystart if node is obs.start else obs.lateend y = ap.get_obs_level(type(obs)) pos[node] = (x, y) labels[node] = str(x) if with_labels else '' break fig = fig or figure() fig.clear() nx.draw(G, pos, fig.gca(), node_color=colors, labels=labels)
def valuation(node, time=None): """ Obtains the heuristic evaluation of an interpretation (a smaller value is better). Currently this function checks the proportion of abstracted observations over the total number of observations that can be abstracted until a specific time point. If the time point is not specified, the time point of the interpretation is considered. See the *time_point* function for details. The function returns a tuple with three values, the first is the relation unexplained/total observations, the second is the amount of time being abstracted by at least one observation, and the third is the number of hypotheses in the interpretation. """ time = time or node.time_point tp, abst, abstime, nhyp = node.past_metrics assert time >= tp if time > tp: abstime += sum(o.earlyend - o.latestart + 1 for o in node.abstracted if ap.get_obs_level(type(o)) == 0) abst += len(node.abstracted) nhyp += len(node.observations) + node.focus.nhyp total = IN.BUF.nobs_before(time) + node.nabd if total == 0: return (0.0, 0.0, 0.0) else: return (1.0 - abst / float(total), -abstime, nhyp)
def _get_obs_descriptor(observation): """ Obtains an observation descriptor (color, level) to represent each of the observations """ colors = {} colors[o.PWave] = ('#66A2A1', 0.2) colors[o.TWave] = ('#E5FF00', 0.2) colors[o.RPeak] = ('#0000FF', 1.0) colors[o.Normal_Cycle] = ('#009C00', 0.2) colors[o.Sinus_Rhythm] = ('#66A2A1', 0.2) colors[o.Cardiac_Rhythm] = ('#66A2A1', 0.2) colors[o.Extrasystole] = ('#897E00', 0.2) colors[o.Tachycardia] = ('#6E0000', 0.2) colors[o.Bradycardia] = ('#3F3F3F', 0.2) colors[o.RhythmBlock] = ('#672E00', 0.2) colors[o.Asystole] = ('#000000', 0.2) colors[o.Bigeminy] = ('#FFAA00', 0.2) colors[o.Trigeminy] = ('#00897E', 0.2) colors[o.Ventricular_Flutter] = ('#844089', 0.2) colors[o.Atrial_Fibrillation] = ('#404389', 0.2) colors[o.Couplet] = ('#D70751', 0.2) colors[o.RhythmStart] = ('#004008', 1.0) colors[o.Noise] = ('#808080', 1.0) colors[o.RDeflection] = ('#008000', 1.0) if isinstance(observation, o.QRS): col = '#0000FF' if not observation.paced else '#FF0000' colors[o.QRS] = (col, 0.2) if isinstance(observation, o.Deflection): lev = 0 if not observation.level else min(observation.level.values()) colors[o.Deflection] = ('#800000', max(0.8 - 0.2 * lev, 0.1)) try: clazz = type(observation) return colors[clazz] + (ap.get_obs_level(clazz), ) except KeyError: #Por defecto, devolvemos gris semitransparente return ('#000000', 0.2, ap.get_obs_level(type(observation)))
def constraints_network_graphviz(interpretation, outfile): """ Draws the constraints network of an interpretation using graphviz. Parameters ---------- interpretation: Interpretation containing the related observations. outfile: File where the figure will be saved """ lst = [] pats = sorted(interpretation.patterns, key=lambda p: -ap.get_obs_level(p.automata.Hypothesis)) for pat in pats: for tnet in pat.temporal_constraints: lst.extend(tnet.get_constraints()) G = pgv.AGraph(directed=True) G.graph_attr['fontsize'] = '7' G.node_attr['style'] = 'filled' G.node_attr['fixedsize'] = 'true' G.node_attr['width'] = '.85' G.node_attr['ordering'] = 'in' for const in lst: G.add_edge(id(const.va), id(const.vb)) #Color assignment observations = interpretation.get_observations() for node in G.nodes_iter(): for obs in observations: if node == str(id(obs.start)) or node == str(id(obs.end)): descriptor = _get_obs_descriptor(obs) node.attr['group'] = type(obs).__name__ #We set some transparency to the color node.attr['color'] = descriptor[0] + 'C0' node.attr['label'] = (str(obs.earlystart) if node == str( id(obs.start)) else str(obs.lateend)) break G.layout(prog='dot', args='-Grankdir=LR') G.draw(outfile)
def advance(interp, focus, pattern): """ Continues the inference by recovering the previous focus of attention, or by going to the next unexplained observation. It also resolves all the postponed matchings. """ max_ap_level = ap.get_max_level() #We can not advance if the focus is an hypothesis of a pattern with #insufficient evidence. newint = None if pattern is not None: if not pattern.sufficient_evidence: return #If there is an observation procedure for the focused hypothesis, the #execution takes place now. elif pattern.automata.obs_proc is not NULL_PROC: patcp = copy.copy(pattern) try: patcp.finish() newint = Interpretation(interp) if (focus.end.value != patcp.hypothesis.end.value or focus.start.value != patcp.hypothesis.start.value): newint.verify_consecutivity_violation(patcp.hypothesis) newint.verify_exclusion(patcp.hypothesis) focus = patcp.hypothesis except InconsistencyError: return #If the new focus is a delayed matching, we solve it at this point. finding = interp.focus.get_delayed_finding(interp.focus.top[0]) if finding is not None: newint = newint or Interpretation(interp) try: newint.focus.pop() pattern = newint.focus.top[1] pred, succ = pattern.get_consecutive(finding) _finding_match(newint, finding, focus, pattern.hypothesis.start.value, pattern.hypothesis.end.value, pred, succ, pattern.abstracts(finding)) #The matched hypothesis is included in the observations list newint.observations = newint.observations.copy() newint.observations.add(focus) except InconsistencyError as error: newint.discard(str(error)) return else: #HINT with the current knowledge base, we restrict the advancement to #observations of the first or the last level, not allowing partial #interpretations in the abstraction level. if (len(interp.focus) == 1 and 0 < ap.get_obs_level(type(focus)) < max_ap_level): return #We just move on the focus. newint = newint or Interpretation(interp) if ap.get_obs_level(type(focus)) == 0: newint.unintelligible = newint.unintelligible.copy() newint.unintelligible.add(focus) #If the focus is a hypothesis, it is added to the observations list if pattern is not None: newint.observations = newint.observations.copy() newint.observations.add(focus) newint.focus.pop() #If we have reached the top of the stack, we go ahead to the next #unexplained observation. if not newint.focus: try: unexp = newint.get_observations( start=focus.earlystart + 1, filt=lambda ev: ev not in newint.unintelligible and ev not in newint.abstracted and ap.is_abducible(type(ev))).next() newint.focus.push(unexp, None) except StopIteration: newint.discard('No unexplained evidence after the current point') return #Finally, we remove old observations from the interpretation, since they #won't be used in following reasoning steps, and they affect the #performance of the searching procedure. oldtime = max(newint.past_metrics.time, newint.focus.earliest_time) - C.FORGET_TIMESPAN newint.remove_old(oldtime) yield newint