#!/usr/bin/env python import sys import random import copy # NB: I have not yet normalized kets vs superpositions in terms of supported functions. # The basics are covered, but there are still gaps I'm too lazy to fill, ATM. class ket(object): def __init__(self,label='?',value=1): self.label = label self.value = float(value) # perhaps look into decimal type. # http://docs.python.org/2/library/decimal.html # nah. float seems appropriate. def __str__(self): return self.display() def type(self): return "ket" def old_display(self): if self.value == 1: return '|' + self.label + '>' else: return str(self.value) + '|' + self.label + '>' # probably slower. Need to do a speed test, and see if significant. # tweaked for exact display, so dump to file and load again don't accidentally zero coeffs. def display(self,exact=False): if self.value == 1: return "|{0}>".format(self.label) elif exact: return str(self.value) + '|' + self.label + '>' else: return "{0:.3f}|{1}>".format(self.value,self.label) def long_display(self): return self.display() def transpose(self): return bra(self.label,self.value) def __add__(self,x): return superposition() + self + x def apply_bra(self,a_bra): return apply_bra_to_ket(a_bra,self) # maybe an apply_projection too? def apply_fn(self,fn): return fn(self) def apply_op(self,context,op): return context.recall(op,self) # see much later in the code for definition of recall. # apply the same op more than once. # especially useful for networks. def apply_op_multi(self,context,op,n): result = copy.deepcopy(self) for k in range(n): result = result.apply_op(context,op) return result def select_elt(self,k): if k != 1 and k != -1: return ket("",0) else: return ket(self.label,self.value) def pick_elt(self): return ket(self.label,self.value) def find_index(self,one): label = one.label if type(one) == ket else one if self.label == label: return 1 return 0 def find_value(self,one): label = one.label if type(one) == ket else one if self.label == label: return self.value return 0 def find_max_coeff(self): return self.value def find_min_coeff(self): return self.value def normalize(self,t=1): result = copy.deepcopy(self) if result.value > 0: result.value = t return result def rescale(self,t=1): result = copy.deepcopy(self) if result.value > 0: result.value = t return result def multiply(self,t): return ket(self.label,self.value*t) def coeff_sort(self): return ket(self.label,self.value) # sigmoids apply to the values of kets, and leave ket labels alone. def apply_sigmoid(self,sigmoid,t1=None,t2=None): result = copy.deepcopy(self) if t1 == None: result.value = sigmoid(result.value) elif t2 == None: result.value = sigmoid(result.value,t1) else: result.value = sigmoid(result.value,t1,t2) return result # do we need a superposition version of this? # implements: similar[op] |x> def similar(self,context,op): f = self.apply_op(context,op) # use apply_op or context.recall() directly? print("f:",f.display()) return context.pattern_recognition(f,op) # if correct, implements: find-topic[op] |x> # yup. Seems to work. def find_topic(self,context,op): return context.map_to_topic(self,op) # implement op3 op2 op1 |x> def merged_apply_op(self,context,ops): result = copy.deepcopy(self) for op in ops.split()[::-1]: # print("op:",op) result = result.apply_op(context,op) return result def count(self): return 1 def count_sum(self): return self.value def number_count(self): return ket("number: 1") def number_count_sum(self): return ket("number: " + str(self.value)) def drop(self): if self.value > 0: return ket(self.label,self.value) else: return ket("",0) # I'm using this in show_range, arithemetic etc, so can feed in sp or ket. # deprecated. Now use x.the_label() # usage: X.ket() # the other half is in superposition. def ket(self): return ket(self.label,self.value) def the_label(self): return self.label class bra(object): def __init__(self,label='?',value=1): self.label = label self.value = float(value) def __str__(self): return self.display() def type(self): return "bra" def old_display(self): if self.value == 1: return '<' + self.label + '|' else: return '<' + self.label + '|' + str(self.value) def display(self): # do we need an "exact" option here? if self.value == 1: return "<{0}|".format(self.label) else: return "<{0}|{1:.3f}".format(self.label,self.value) def transpose(self): return ket(self.label,self.value) # also at some stage I suppose a transpose of a superposition. # so I guess we would need to distinguish between a bra-superposition and a ket-superposition. def transpose(x): return x.transpose() # need to think on how we want |> and <| to behave. # eg, currently <*||> returns 1. May want 0. def labels_match(label_1,label_2): print("label_1:",label_1) print("label_2:",label_2) truth_var = True one = label_1.lower() # make label compare case insensitive two = label_2.lower() # hrrmm... may not want this .... if one[0] == '!': # for now only consider bra's with one = one[1:] # though it is not much work to extend it. truth_var = False print("one:",one) print("two:",two) if one == two: return truth_var a_cat = one.split(': ') b_cat = two.split(': ') if a_cat[-1] == '*': new_a_cat = a_cat[:-1] new_b_cat = b_cat[:len(new_a_cat)] if new_a_cat == new_b_cat: return truth_var else: return not truth_var if b_cat[-1] == '*': new_b_cat = b_cat[:-1] new_a_cat = a_cat[:len(new_b_cat)] if new_b_cat == new_a_cat: return truth_var else: return not truth_var return not truth_var def extract_category_value(label): one = label.split(': ') value = one[-1] category = ": ".join(one[:-1]) return category, value def apply_bra_to_ket(a_bra,a_ket): if type(a_bra) == str: # this is so we don't need bra("person: Fred") everywhere. a_bra = bra(a_bra) # we can use "person: Fred" directly. # maybe the same conversion from string for a_ket?? elif type(a_bra) == ket: # this so we can fudge and pass in a ket that acts like a bra. a_bra = bra(a_bra.label,a_bra.value) star = "*" if a_bra.value == 1 or a_ket.value == 1: star = "" print(a_bra.display() + star + a_ket.display()) if labels_match(a_bra.label,a_ket.label): return a_bra.value * a_ket.value else: return 0 class superposition(object): def __init__(self): self.data = [] def __str__(self): return self.display() def type(self): return "(" + " + ".join(x.type() for x in self.data) + ")" def add_ket(self,a_ket): if a_ket.label == '': # treat |> as the identity ket return match = None for x in self.data: if x.label == a_ket.label: match = True x.value += a_ket.value break if match == None: new_ket = ket(a_ket.label,a_ket.value) self.data.append(new_ket) def __add__(self,elt): result = copy.deepcopy(self) if type(elt) == ket: result.add_ket(elt) if type(elt) == superposition: for x in elt.data: result.add_ket(x) return result # a version of add that does not add kets with the same label def clean_add_ket(self,a_ket): if a_ket.label == '': return # if len([x.label for x in self.data if x.label == a_ket.label]) == 0: if sum(1 for x in self.data if x.label == a_ket.label) == 0: self.data.append(ket(a_ket.label,a_ket.value)) # same as above, but also works for superpositions: def clean_add(self,one): if type(one) == ket: self.clean_add_ket(one) if type(one) == superposition: for x in one.data: self.clean_add_ket(x) def display(self,exact=False): if len(self.data) == 0: return '|>' return " + ".join(x.display(exact) for x in self.data) def long_display(self): if len(self.data) == 0: return '|>' return "\n".join(str("%.3f" % x.value) + ' |' + x.label + '>' for x in self.data) def apply_bra(self,a_bra): return sum(apply_bra_to_ket(a_bra,x) for x in self.data) # maybe a version where the projection is of more than one element? def apply_projection(self,a_bra): if len(self.data) == 0: return 0 # should this be ket("")? Or ket("",0) result = copy.deepcopy(self) for x in result.data: x.value = apply_bra_to_ket(a_bra,x) return result # we don't need the next two. # use apply_fn(extract_value), and apply_fn(extract_category), and apply_fn(apply_value) instead. # def apply_extract_value(self): # result = copy.deepcopy(self) ## result.data = [x.apply_extract_value() for x in result.data ] # result.data = [extract_value(x) for x in result.data ] # return result # def apply_extract_category(self): # result = copy.deepcopy(self) ## result.data = [x.apply_extract_category() for x in result.data ] # result.data = [extract_category(x) for x in result.data ] # return result # for the family of functions that apply to kets. # mapping ket -> ket. # this is buggy if fn(x) actually returns a superposition! # it sort of works, but creates wierd bugs down the line. # Now, if we had a mixed type that can handle lists of not just # kets, but superpositions, sequences, and everything else, then it would be fine. def buggy_apply_fn(self,fn): result = superposition() result.data = [fn(x) for x in self.data ] # this behaviour is more inline of what you would return result # expect from the sequence type (not yet implemented!) # let's try and write a non-buggy version, but without needing to collapse the kets. def apply_fn(self,fn): result = superposition() for x in self.data: r = fn(x) if type(r) == ket: result.data.append(r) if type(r) == superposition: result.data += r.data return result # keep this variant distinct from apply_fn(fn) for now. # also, now fn can map ket -> ket, and also ket -> superposition. def apply_fn_collapse(self,fn): result = superposition() for x in self.data: result += fn(x) return result # if there are repeated elements in the superposition, add them up. # This is buggy, some of the time! Look into it! # I think it might just be the apply_fn() with fn() returning a superposition, is the bug. # Yup. The bug was in apply_fn(). Fixed now. # eg, ((ket) + (ket + ket + ket + ket) + (ket + ket)) # would cause collapse() to fail. def collapse(self): return superposition() + self def apply_op(self,context,op): result = superposition() for x in self.data: result += context.recall(op,x) return result # apply the same op more than once. # especially useful for networks. def apply_op_multi(self,context,op,n): result = copy.deepcopy(self) for k in range(n): result = result.apply_op(context,op) return result def count(self): return len(self.data) def count_sum(self): return sum(x.value for x in self.data) def number_count(self): result = len(self.data) return ket("number: " + str(result)) def number_count_sum(self): result = sum(x.value for x in self.data) # does this bug out if len(self.data) == 0? return ket("number: " + str(result)) def drop(self): result = copy.deepcopy(self) result.data = [x for x in result.data if x.value > 0 ] return result def drop_below(self,t): result = copy.deepcopy(self) result.data = [x for x in result.data if x.value >= t ] return result def drop_above(self,t): result = copy.deepcopy(self) result.data = [x for x in result.data if x.value <= t ] return result # NB: we use: 1 <= k <= len, not 0 <= k < len to access ket objects. # NB: though we still use -1 for the last element, -2 for the second last element, etc. def select_elt(self,k): # result = copy.deepcopy(self) # if k >= 1 and k <= len(result.data): # result.data = [result.data[k - 1]] # else: # result.data = [] # return result if k >= 1 and k <= len(self.data): return copy.deepcopy(self.data[k - 1]) elif k < 0: return copy.deepcopy(self.data[k]) else: return ket("",0) # now with the change to select_elt(k), if you want to select a single elt, but still return a superposition, # you need to do select_range(k,k). # what if we want to index from the end of the list? cf, tail -3 or something? def select_range(self,a,b): a = max(1,a) - 1 b = min(b,len(self.data)) result = superposition() result.data = copy.deepcopy(self.data[a:b]) return result def delete_elt(self,k): result = copy.deepcopy(self) result.data = [x for i,x in enumerate(result.data) if i != (k-1) ] return result # maybe a version of this that takes into account the coeffs, and makes a weighted random choice. # Yeah. For a start, see: http://stackoverflow.com/questions/3679694/a-weighted-version-of-random-choice def pick_elt(self): result = copy.deepcopy(self) return random.choice(result.data) # NB: this is case sensitive, since |x> != |X> # NB: in some cases find_index() gives very different answers than set_mbr(), in terms of yes or no of membership. # It basically boils down to: # labels_match(x.label,label) vs x.label == label (first is used, indirectly, in set_mbr(), second in find_index() ) # # Also recall: ## test for set membership of |x> in |X> #def set_mbr(x,X,t=1): # return X.apply_bra(x) >= t # def find_index(self,one): label = one.label if type(one) == ket else one for k,x in enumerate(self.data): if x.label == label: return k + 1 return 0 # yeah, 0 for not in the superposition. def find_value(self,one): label = one.label if type(one) == ket else one for x in self.data: if x.label == label: return x.value return 0 def normalize(self,t=1): result = copy.deepcopy(self) the_sum = sum(x.value for x in result.data) if the_sum > 0: for x in result.data: x.value = t*x.value/the_sum return result def rescale(self,t=1): if len(self.data) == 0: return ket("") result = copy.deepcopy(self) the_max = max(x.value for x in result.data) if the_max > 0: for x in result.data: x.value = t*x.value/the_max return result def multiply(self,t): result = copy.deepcopy(self) for x in result.data: x.value = x.value*t return result def reverse(self): result = copy.deepcopy(self) result.data.reverse() return result def shuffle(self): result = copy.deepcopy(self) random.shuffle(result.data) return result # with thanks to this page: https://wiki.python.org/moin/HowTo/Sorting # maybe we want the reverse, biggest first, not last? def coeff_sort(self): result = superposition() # result.data = sorted(self.data, key=lambda x: x.value) result.data = sorted(self.data, key=lambda x: x.value,reverse=True) return result def ket_sort(self): result = superposition() # result.data = sorted(self.data, key=lambda x: x.label.lower()) result.data = sorted(self.data, key=lambda x: x.label.lower(),reverse=True) return result def find_max_elt(self): if len(self.data) == 0: return ket("",0) the_max = max(x.value for x in self.data) for x in self.data: if x.value == the_max: return ket(x.label,x.value) print("I shouldn't be here in find_max_elt.") def find_min_elt(self): if len(self.data) == 0: return ket("",0) the_min = min(x.value for x in self.data) for x in self.data: if x.value == the_min: return ket(x.label,x.value) print("I shouldn't be here in find_min_elt.") def find_max(self): if len(self.data) == 0: return superposition() the_max = max(x.value for x in self.data) result = copy.deepcopy(self) result.data = [x for x in result.data if x.value == the_max] return result def find_min(self): if len(self.data) == 0: return superposition() the_min = min(x.value for x in self.data) result = copy.deepcopy(self) result.data = [x for x in result.data if x.value == the_min] return result def find_max_coeff(self): if len(self.data) == 0: return 0 # maybe it should return None? return max(x.value for x in self.data) def find_min_coeff(self): if len(self.data) == 0: return 0 return min(x.value for x in self.data) # sigmoids apply to the values of kets, and leave ket labels alone. def apply_sigmoid(self,sigmoid,t1=None,t2=None): result = copy.deepcopy(self) if t1 == None: for x in result.data: x.value = sigmoid(x.value) elif t2 == None: for x in result.data: x.value = sigmoid(x.value,t1) else: for x in result.data: x.value = sigmoid(x.value,t1,t2) return result # deprecated. This use case is now: X.the_label() # usage: X.ket() def ket(self): if len(self.data) == 0: return ket("",0) tmp = self.data[0] return ket(tmp.label,tmp.value) def the_label(self): if len(self.data) == 0: return "" return self.data[0].label def display(x): return x.display() # some sigmoids: def clean(x): if x <= 0: return 0 else: return 1 # this one is so common that it is implemented in superposition as .drop_below(t) def threshold_filter(x,t): if x < t: return 0 else: return x def binary_filter(x): if x <= 0.96: return 0 else: return 1 def not_binary_filter(x): if x <= 0.96: return 1 else: return 0 def pos(x): if x <= 0: return 0 else: return x def NOT(x): if x <= 0.04: return 1 else: return 0 # otherwise known as the Goldilock's function. # not too hot, not too cold. def xor_filter(x): if 0.96 <= x and x <= 1.04: return 1 else: return 0 def mult(x,t): return x*t # this is another type of "Goldilock function" # the in-range sigmoid: def in_range(x,a,b): if a <= x and x <= b: return x else: return 0 # we need this since pattern_recognition() requires simm(). # bug's out if I put this at the top of the page. from the_semantic_db_functions import * # use case for this is "declining" def extract_op_type(op): # if type(op) == ket: # op_type = op.label.split("op: ")[-1] ## print("ket-type in extract_op_type:",op_type) # return op_type # else: # assume if not a ket, then a string. # return op return op.label.split("op: ")[-1] if type(op) == ket else op # now, closer to the main event: class new_context(object): def __init__(self,name): # print("new context \"",name,"\" defined.",sep='') self.name = name self.known_kets = [] self.rules = {} self.rule_list = {} # this mess so we can keep track of the order of rules. # maybe a .display() function in here somewhere. # show some basics of what is known. # question: should labels be case sensitive? # # op is a string that is the text name for an operator ("" is a valid op name - I'll explain meaning later!) # "" op roughly corresponds to "what springs to mind about X?" # label is either a ket, or the label of that ket. (maybe give the variable a better name!) # rule can be anything! def learn(self,op,label,rule,add_learn=False): # potentially we could put a op_type = extract_op_type(op) here. # but most use cases, op is already a string, so no point. label = label.label if type(label) == ket else label if type(rule) == str: # if a rule is a single ket, then allows us to omit the ket("label") and use "label" directly. rule = ket(rule) if type(rule) == ket: if rule.label == '': # don't learn empty rules. return # I'm not sure we always want this. # what if it is an empty superposition? if type(rule) == superposition: # Yup. Added that for now. if len(rule.data) == 0: return if label not in self.known_kets: self.known_kets.append(label) self.rules[label] = {} self.rule_list[label] = ["supported-ops"] self.rules[label]["supported-ops"] = superposition() self.rules[label]["supported-ops"].clean_add_ket(ket("op: " + op)) # keep a record of all rules we learn. if not add_learn: # standard learn, where we overwrite old knowledge. self.rules[label][op] = rule # corresponds to op |x> => |y> else: # BTW, we have a potential pass by reference bug with " = rule" here! if op not in self.rules[label]: # op not in dict self.rules[label][op] = superposition() else: # op in dict self.rules[label][op] = superposition() + self.rules[label][op] # for clean_add to work, need superposition, not ket. self.rules[label][op].clean_add(rule) # we use clean_add() so we don't add duplicates. see: +=> if op in self.rule_list[label]: # keep a record of the order we learn new ops. self.rule_list[label].remove(op) # and update if an op is redefined. self.rule_list[label].append(op) def add_learn(self,op,label,rule): # corresponds to op |x> +=> |y> return self.learn(op,label,rule,True) # found a sort-of bug. What happens if op is a superposition? # may fix with select_elt(1), but a lot of work probably, to tweak all the other dependent functions! def recall(self,op,label): value = label.value if type(label) == ket else 1 # value = 1 # use this to switch off the apply-value feature op_type = extract_op_type(op) label = label.label if type(label) == ket else label if label not in self.known_kets: # print("In context \"",self.name,"\": I don't know anything about \"",label,"\".",sep='') out_string = "In context \"{context}\": I don't know anything about \"{label}\"." print(out_string.format(context=self.name,label=label)) return ket("") # this is the human equivalent of "I don't know", the empty ket: |> if op_type not in self.rules[label]: if op_type == 'id': # if not otherwise defined 'id' is the identity operator. return ket(label) # id |*> => |_self> # print("In context \"",self.name,"\": I have no knowledge of the \"",op_type,"\" operator applied to \"",label,"\".",sep='') out_string = "In context \"{context}\": I have no knowledge of the \"{op}\" operator applied to \"{label}\"." print(out_string.format(context=self.name,op=op_type,label=label)) return ket("") if value == 1: # print("branch 1") return self.rules[label][op_type] # print("branch mult") return self.rules[label][op_type].apply_sigmoid(mult,value) # this mult,value thing neatly implements the idea # that you can only reason so many steps of informal logic # before you are completely uncertain of your conclusions. # op is the string name for an operator # label is either a ket, or a ket.label # # Do we need some tests in here for op, label existence? # Nah, I guess recall takes care of that. I presume. def dump_rule(self,op,label,exact=False): op_type = extract_op_type(op) name = label if type(label) == ket else ket(label) return op_type + " " + name.display() + " => " + self.recall(op,label).display(exact) def dump_all_rules(self,label,exact=False): label = label.label if type(label) == ket else label if label not in self.known_kets: return "" return "\n".join(self.dump_rule(op,label,exact) for op in self.rule_list[label] ) def dump_sp_rules(self,sp,exact=False): # not super clear what this function does .... if type(sp) == superposition: return "\n\n".join(self.dump_all_rules(x,exact) for x in sp.data ) else: return self.dump_all_rules(sp,exact) def dump_universe(self,exact=False): # context_string = "context: " + self.name context_string = "|context> => |context: " + self.name + ">" sep = "\n----------------------------------------\n" # return context_string + sep + "\n-----\n".join(self.dump_all_rules(x) for x in self.known_kets ) + sep return sep + context_string + "\n\n" + "\n\n".join(self.dump_all_rules(x,exact) for x in self.known_kets ) + sep def create_rule_inverse(self,op,label): op = op.label.split("op: ")[-1] if type(op) == ket else op label = label.label if type(label) == ket else label if label not in self.known_kets: return if op not in self.rules[label]: return if op.startswith("inverse-"): # seems pointless to take the inverse of an inverse. return # presuming that: inverse-inverse-op == op rule = self.rules[label][op] if type(rule) == ket: if rule.label != '': # don't make the inverse for empty kets |> self.add_learn("inverse-" + op,rule,ket(label)) if type(rule) == superposition: for x in rule.data: if x.label != '': self.add_learn("inverse-" + op,x,ket(label)) def create_all_rule_inverse(self,label): label = label.label if type(label) == ket else label if label not in self.known_kets: return for op in self.rule_list[label]: self.create_rule_inverse(op,label) # if we have it right this should be idempotent. # may have an infinite loop if our molecules of knowledge have cycles. # need to test, and if a problem, fix def create_universe_inverse(self): for x in self.known_kets: self.create_all_rule_inverse(x) # old version, that uses "pattern" as the hard-wired in operator. def old_verbose_pattern_recognition(self,pattern): print("pattern:",pattern.display()) for x in self.known_kets: if "pattern" in self.rules[x]: print("recall:",self.recall("pattern",x).display()) candidate_pattern = self.recall("pattern",x) candidate_simm = silent_simm(pattern,candidate_pattern) print("simm:",candidate_simm) # old version, that uses "pattern" as the hard-wired in operator. def old_pattern_recognition(self,pattern): result = superposition() for x in self.known_kets: if "pattern" in self.rules[x]: candidate_pattern = self.recall("pattern",x) value = silent_simm(pattern,candidate_pattern) result += ket(x,value) return result def pattern_recognition(self,pattern,op="pattern",t=0): op = op.label.split("op: ")[-1] if type(op) == ket else op result = superposition() for x in self.known_kets: if op in self.rules[x]: candidate_pattern = self.recall(op,x) value = silent_simm(pattern,candidate_pattern) if value > t: result.data.append(ket(x,value)) return result.coeff_sort() def verbose_pattern_recognition(self,pattern,op="pattern"): op = op.label.split("op: ")[-1] if type(op) == ket else op print("pattern:",pattern.display()) for x in self.known_kets: if op in self.rules[x]: print("recall:",self.recall(op,x).display()) candidate_pattern = self.recall(op,x) candidate_simm = silent_simm(pattern,candidate_pattern) print("simm:",candidate_simm) # need unlearn stuff here. eg, unlearn-rule, unlearn-label, etc. def map_to_topic(self,e,op,t=0): # do we need the op = op.label.split("op: ... stuff here? result = superposition() for x in self.known_kets: if op in self.rules[x]: frequency_list = self.recall(op,x) value = normed_frequency_class(e,frequency_list) if value > t: result.data.append(ket(x,value)) return result.normalize(100).coeff_sort() class context_list(object): def __init__(self,name): self.name = name c = new_context(name) self.data = [c] self.index = 0 def set(self,name): match = False for k,context in enumerate(self.data): if context.name == name: self.index = k match = True break if not match: c = new_context(name) self.data.append(c) self.index = len(self.data) - 1 def show_context_list(self): # maybe include a count of the number of kets known to that context text = "context list:\n" for k,context in enumerate(self.data): pre = "* " if k == self.index else " " # text += pre + context.name + "\n" text += pre + context.name + " (" + str(len(context.known_kets)) + ")\n" return text def context_name(self): return self.data[self.index].name def learn(self,op,label,rule): self.data[self.index].learn(op,label,rule) def add_learn(self,op,label,rule): self.data[self.index].add_learn(op,label,rule) def recall(self,op,label): return self.data[self.index].recall(op,label) def dump_all_rules(self,label,exact=False): return self.data[self.index].dump_all_rules(label,exact) def dump_sp_rules(self,label,exact=False): return self.data[self.index].dump_sp_rules(label,exact) def dump_universe(self,exact=False): return self.data[self.index].dump_universe(exact) def create_universe_inverse(self): self.data[self.index].create_universe_inverse() def create_multiverse_inverse(self): for context in self.data: context.create_universe_inverse() def pattern_recognition(self,pattern,op="pattern",t=0): return self.data[self.index].pattern_recognition(pattern,op,t) def verbose_pattern_recognition(self,pattern,op="pattern"): return self.data[self.index].verbose_pattern_recognition(pattern,op) def map_to_topic(self,e,op,t=0): return self.data[self.index].map_to_topic(e,op,t) def global_recall(self,op,label): result = superposition() for context in self.data: result += context.recall(op,label) return result def dump_multiverse(self,exact=False): result = "" for context in self.data: result += context.dump_universe(exact) return result