"""Concept Models. """ from __future__ import generators import sys __all__ = ['Concept', 'Morphism', 'Operation'] def hacksetname(obj): f = sys._getframe(2) obj._globals_ = f.f_globals def hackgetname(obj): g = obj._globals_ for name, value in g.iteritems(): if value is obj: break else: name = '?' return '%s.%s' % (g.get('__name__'), name) class Concept: "A concept (with no particular structure)." def __init__(self): self.eq = Operation([self, self], "Return another representation of the same object.") hacksetname(self) def __call__(self, obj, morphism=0): return ConceptInstance(self.in_context(morphism), obj) def __add__(self, other): if other.gettower().base is not self: raise TypeError("cannot compose %r and %r" % (self, other)) return other def __repr__(self, prefix=''): return '<%sconcept %s>' % (prefix, hackgetname(self)) def gettower(self): return Tower(self, [], self) def in_context(self, t): if isinstance(t, (Concept, Morphism, Tower)): if t.gettower().abstract is not self: raise TypeError("%s has abstract %r, expected %r" % ( t.__class__.__name__, t.gettower().abstract, self)) return t elif t == 0: return self else: raise TypeError("expected a Concept, Morphism or Tower, " "got %r instead" % type(t).__name__) class Morphism: "A morphism between a base and an abstract concept." def __init__(self, base, abstract, doc): self.base = base self.abstract = abstract self.__doc__ = doc def __add__(self, other): return self.gettower() + other def gettower(self): return Tower(self.base, [self], self.abstract) def __repr__(self, prefix=''): return '<%smorphism %s [%s --> %s]>' % (prefix, hackgetname(self), hackgetname(self.base), hackgetname(self.abstract)) class Tower: "A composition of morphisms." def __init__(self, base, morphisms, abstract): self.base = base self.morphisms = morphisms self.abstract = abstract def __add__(self, other): t1 = self t2 = other.gettower() if t1.abstract is not t2.base: raise TypeError("cannot compose %r and %r" % (self, other)) return Tower(t1.base, t1.morphisms + t2.morphisms, t2.abstract) def gettower(self): return self def __repr__(self): if len(self.morphisms) == 0: return self.base.__repr__('tower ') if len(self.morphisms) == 1: return self.morphisms[0].__repr__('tower ') return ' %s]>' % ( '+'.join([hackgetname(m) for m in self.morphisms]), hackgetname(self.base), hackgetname(self.abstract)) def singlestep_casts(self): """Enumerates (target_tower, conversion_function).""" if self.morphisms: lastm = self.morphisms[-1] more_concrete = Tower(self.base, self.morphisms[:-1], lastm.base) for t, c in more_concrete.singlestep_casts(): yield t + lastm, c for (t_in, t_out), c in self.abstract.eq.impls.iteritems(): t_in = t_in.gettower() assert t_in.abstract is self.abstract morphisms = t_in.morphisms if morphisms == self.morphisms[-len(morphisms):]: if morphisms == self.morphisms: yield t_out, c elif c is None: t = Tower(self.base, self.morphisms[:-len(morphisms)], t_in.base) yield t + t_out, None def rec_casts(self): """Enumerates (target_tower, list_of_conversion_functions).""" pending = [] def reg(tower, clist, seen={}): if tower not in seen: seen[tower] = True pending.append((tower, clist)) return True else: return False if reg(self, []): yield self, [] for from_tower, clist in pending: from_tower = from_tower.gettower() for target_tower, c in from_tower.singlestep_casts(): clist2 = clist[:] if c: clist2.append(c) if reg(target_tower, clist2): yield target_tower, clist2 def __eq__(self, other): other = other.gettower() return self.base is other.base and self.morphisms == other.morphisms def __ne__(self, other): return not (self == other) def __hash__(self): data = [self.base] + self.morphisms return hash(tuple(data)) Abstract = object() # tag class Operation: "An operation between concepts (with a repository of implementations)." def __init__(self, concepts, doc=None, implement=Abstract): self.concepts = concepts self.__doc__ = doc self.implement = implement self.more_concrete = {} self.more_abstract = {} def __setitem__(self, morphisms, op): if len(morphisms) != len(self.concepts): raise TypeError, "expected %d morphisms" % len(self.concepts) towers = [c.in_context(t) for c, t in zip(self.concepts, morphisms)] if not isinstance(op, Operation): op = Operation([t.gettower().base for t in towers], implement=op) towers = tuple(towers) # keep the links minimal: for any three operations Op1, Op2, Op3 # with Op1 more concrete than Op2 more concrete than Op3, there is # only a link Op1-Op2 and a link Op2-Op3. self.more_concrete[towers] = op op.more_abstract[towers] = self def identical(self, morphism1, morphism2): self[morphism1, morphism2] = None self[morphism2, morphism1] = None def __call__(self, *args): if len(self.concepts)-1 != len(args): raise TypeError, "expected %d args" % (len(self.concepts)-1) for a, c in zip(args, self.concepts): if a.tower.abstract is not c: raise TypeError, "expected %r, got %r" % (c, a.tower.abstract) for towers, fn in self.impls.iteritems(): try: clists = [a.cast_to(t) for a, t in zip(args, towers)] except CastError: pass else: def cname(f): return getattr(f, '__name__', `f`) converted = [] for a, clist in zip(args, clists): a = a.obj for c in clist: print ' * %s(%.80r)' % (cname(c), a) a = c(a) converted.append(a) print '** %s(%s)' % (cname(fn), ', '.join(['%.80r' % a for a in converted])) a = fn(*converted) return towers[-1](a) raise TypeError, "cannot find a way to call this" def implementations(self): "Generates all available implementations for this operation." class CastError(Exception): "Cannot cast the abstract object to the given morphism." class Abstract(object): DEBUG = False def __init__(self, tower, obj): if isinstance(obj, Abstract): self.tower = obj.tower + tower self.obj = obj.obj else: self.tower = tower.gettower() self.obj = obj def __repr__(self): if self.tower.morphisms: return '<%r as %s [%s --> %s]>' % (self.obj, '+'.join([m.name for m in self.tower.morphisms]), self.tower.base.name, self.tower.abstract.name) else: return '<%r as %s>' % (self.obj, self.tower.base.name) def cast_to(self, target_tower): if self.DEBUG: print 'target is:', target_tower for tower, clist in self.tower.rec_casts(): if self.DEBUG: print 'trying:', tower if tower == target_tower: if self.DEBUG: print 'ok.' return clist if self.DEBUG: print 'failed.' raise CastError(self, target_tower) def as(self, target_tower=()): clist = self.cast_to(self.tower.abstract.in_context(target_tower)) def cname(f): return getattr(f, '__name__', `f`) a = self.obj for c in clist: print ' * %s(%.80r)' % (cname(c), a) a = c(a) return a