class AddTag(Codelet): taggee: R[Node] = Ref('taggee') tag: R[Node] = Ref('tag') def run( # type: ignore[override] self, fm: FARGModel, taggee: Node, tag: Node, sources: Sources ) -> CodeletResults: if not fm.has_node(taggee): if isinstance(taggee, CellRef): # HACKish and needs UT sources = fm.run_codelet_and_follow_ups( Build(to_build=taggee), sources ) if fm.has_node(taggee): if isclass(tag): # TODO Supply arguments to tag ctor tag = tag() # type: ignore[operator] sources = fm.run_codelet_and_follow_ups( Build(to_build=tag), sources ) tag = first(fm.look_up_by_name('built', sources)) # TODO What if tag is None now? fm.add_tag(taggee, tag) else: raise TaggeeDoesNotExist(taggee=taggee) return None
class MakeVariantFromAvails(Codelet): agent: R[Agent] = Ref('agent') cellref: R[CellRef] = Ref('source') avails: R[Tuple[Value, ...]] = Ref('avails') # These values were avail; indices match indices in seeker's request unavails: R[Tuple[Value, ...]] = Ref('unavails') # These values were unavail; indices match indices in seeker's request def run( # type: ignore[override] self, fm: FARGModel, cellref: CellRef, agent: Agent, # the Agent to make a variant of avails: Tuple[Value, ...], unavails: Tuple[Value, ...], running_agent: Optional[Agent]=None ) -> CodeletResults: true_avails = list(as_iter(cellref.avails)) new_operands: List[Value] = [] for a, u in zip(avails, unavails): # TODO zip_longest? if a is not None: if a in true_avails: new_operands.append(a) true_avails.remove(a) else: self.fill_unavail_from_avails(a, new_operands, true_avails) if u is not None: self.fill_unavail_from_avails(u, new_operands, true_avails) # TODO Don't make a new agent that's already there if not isinstance(agent, Agents.Consumer): return None # TODO raise exception? else: return Build( # TODO Standardize order of operands when commutative? to_build=replace(agent, operands=tuple(new_operands)), builder=running_agent ) @classmethod def fill_unavail_from_avails( cls, u: Value, new_operands: List[Value], true_avails: List[Value] ) -> None: '''Chooses one or more replacements for 'u' from 'true_avails', removes them from 'true_avails', and appends them to 'new_operands'.''' ua: Sequence[Value] = choose_most_similar(true_avails, u) if ua: new_operands += ua for v in ua: true_avails.remove(v) else: pass # TODO raise an exception?
class Consume(Codelet): operator: R[Operator] = Ref('operator') operands: R[Tuple[Value, ...]] = Ref('operands') source: R[CellRef] = Ref('source') result_in: R[str] = 'result' def run( # type: ignore[override] self, fm: FARGModel, operator: Operator, operands: Tuple[Value, ...], source: CellRef, result_in: str) -> CodeletResults: return dict([(result_in, operator.consume(source, operands)), ('dest', source.next_cellref())])
class Paint(Codelet): '''Paints a value in a Canvas cell.''' dest: R[CellRef] = Ref('dest') value: R[Value] = Ref('value') sk: R[Codelets] = NewState(Ref('running_agent'), Succeeded) fk: R[Codelets] = NewState(Ref('running_agent'), Snag) def run( # type: ignore[override] self, fm, dest: CellRef, value: Value, running_agent: Optional[Agent], sk: Optional[Codelet] ) -> CodeletResults: #print('PAINT', fm.a(running_agent), running_agent) fm.paint(dest, value, running_agent) return None
class Consumer(Agent): Q = TypeVar('Q', bound='Consumer') operator: Union[Operator, None] = None operands: Union[Tuple[Value, ...], None] = None source: Union[CellRef, None] = None # where to get operands # TODO rm dest? #dest: Union[CellRef, None] = None # where to paint result wake: Codelets = (Consume(operator=Ref('operator'), operands=Ref('operands'), source=Ref('source'), result_in='result'), BuildLitPainter(value=Ref('result')), Sleep(agent=Ref('running_agent'))) # Another possible approach, breaking down Consume into smaller codelets: #TakeOperands(operands=Ref('operands'), cellref=Ref('source')), #ComputeResult(), delegate_succeeded: Codelets = ISucceeded() def features_of(self) -> Iterable[Node]: for operand in as_iter(self.operands): yield Before(operand) if self.operator: yield self.operator if self.operands and self.operator: result = self.operator(*self.operands) yield After(result) def short(self) -> str: cl = self.__class__.__name__ s = short(self.operator).join(short(o) for o in as_iter(self.operands)) return f'{cl}({s})' @classmethod def make(cls: Type[Q], operator: Union[Operator, None], operands: Union[Tuple[Value, ...], None]) -> Q: return cls(operator=operator, operands=operands) @classmethod def make_table(cls, rands1: Iterable[int], rands2: Iterable[int], rators: Iterable[Operator]) -> Iterable['Consumer']: for rand1 in rands1: for rand2 in rands2: for rator in rators: if rand1 >= rand2: result = rator(rand1, rand2) if result != rand1 and result != rand2: yield cls(operator=rator, operands=(rand1, rand2))
class BuildLitPainter(Codelet): value: R[Value] = Ref('value') dest: R[CellRef] = Ref('dest') def run( # type: ignore[override] self, fm: FARGModel, value: Value, dest: CellRef, running_agent: Optional[Agent] ) -> CodeletResults: fm.build( Agents.LitPainter(value=value, dest=dest), builder=running_agent ) return None
class Build(Codelet): '''Builds one or more nodes that serve as companions for the acting node.''' to_build: R[Nodes] = None builder: R[Actor] = Ref('running_agent') def run( # type: ignore[override] self, fm: FARGModel, builder: Optional[Actor], to_build: Nodes, sources: Sources ) -> CodeletResults: built: List[Node] = [] for node in as_iter(to_build): node = fm.replace_refs(node, sources) node = fm.build(node, builder=builder) built.append(node) ''' if builder: return NewState(builder, Wake) else: return None ''' return {'built': set(built)} def short(self) -> str: cl = self.__class__.__name__ return f'{cl}({short(self.to_build)})'
class QuerySlipnetForDelegate(Codelet): qargs: R[QArgs] = Ref('qargs') #sk: R[Codelets] = Sleep(Ref('running_agent')) TODO def run( # type: ignore[override] self, fm: FARGModel, running_agent: Optional[Agent], qargs: QArgs, # TODO Require at least one QArg? sources: Sources ) -> CodeletResults: kwargs = fm.mk_slipnet_args(qargs, sources) slipnet_results = fm.pulse_slipnet( alog=fm.start_alog((running_agent, self)), **kwargs ) if not slipnet_results: raise NoResultFromSlipnet(qargs=qargs) return [ Build( to_build=fm.try_to_fill_nones(node, sources, running_agent), builder=running_agent ) for node in slipnet_results ] """
def test_agent_replace_refs(self) -> None: fm = FARGModel() ag1 = ag.replace_refs(fm, None) self.assertEqual( ag1, DummyAgent(agstr='FROM AGENT', born=DummyCodelet(late_bound=Ref('agstr'))))
class RaiseException(Codelet): exctype: R[Type[Exception]] = Ref('exctype') def run( # type: ignore[override] self, fm: FARGModel, exctype: Type[Exception], sources: Sources ) -> CodeletResults: raise exctype(**fm.mk_func_args(exctype, sources)) # type: ignore[call-arg]
class Sleep(Codelet): agent: R[Actor] = Ref('agent') sleep_duration: int = 3 def run( # type: ignore[override] self, fm: FARGModel, agent: Agent, sleep_duration: int ) -> CodeletResults: fm.sleep(agent, sleep_duration) return None
class VariantMakerFromAvails(Agent): agent: R[Agent] = Ref('agent') # The agent to make a variant of cellref: R[CellRef] = Ref('source') avails: R[Tuple[Value, ...]] = Ref('avails') # These values were avail; indices match indices in seeker's request unavails: R[Tuple[Value, ...]] = Ref('unavails') # These values were unavail; indices match indices in seeker's request wake: Codelets = (MakeVariantFromAvails(Ref('agent'), Ref('source'), Ref('avails'), Ref('unavails')), Sleep(agent=Ref('running_agent'))) def short(self) -> str: cl = self.__class__.__name__ return f'{cl}({short(self.agent)}, {short(self.cellref)}, {short(self.avails)}, {short(self.unavails)})' def features_of(self) -> Iterable[Node]: yield Desnag(ValuesNotAvail)
class Want(Agent): startcell: R[CellRef] = Ref('startcell') target: R[Value] = Ref('target') on_success: R[Codelets] = Ref('on_success') born: Codelets = ( Build(AvailDetector()), Build( DeadEndDetector(on_success=AddTag( tag=DeadEnd(), # TODO for_goal = this Want taggee=Ref('dead_end')))), NewState(agent=Ref('running_agent'), state=Wake)) wake: Codelets = (FindLastPaintedCell(), QuerySlipnetForDelegate( qargs=(QBeforeFromAvails(Ref('startcell')), QAfter(Ref('target')), SearchFor(Agent), ExcludeExisting())), Sleep(agent=Ref('running_agent'))) #delegate_succeeded: Codelets = RaiseException(KeyError) def short(self) -> str: cl = self.__class__.__name__ return f'{cl}({short(self.target)}, {short(self.startcell)})'
class DeadEnd(Tag): for_goal: R[Node] = Ref('for_goal') # TODO Inherit from something that has this. def short(self) -> str: cl = self.__class__.__name__ return f'{cl}({dict_str(as_dict(self), xform=short)})' """ def extend(self, fm: FARGModel, sources: Sources) -> None: '''Extend the taggees of this tag as far as possible. So, a DeadEnd that tags a CellRef should be extended to tag the CellRef's LitPainter and Consumer that produced the LitPainter.''' taggees = set(fm.taggees_of(self)) pending: Set[Node] = set(taggees) new_taggees: Set[Node] = set() while pending: for old_node in pending: for new_node in self.also_needs_tagging1(fm, old_node): if new_node not in taggees: fm.add_tag(new_node, self) new_taggees.add(new_node) taggees |= new_taggees pending = new_taggees new_taggees.clear() """ @classmethod def also_tag(cls, fm: FARGModel, node: Node) -> Iterable[Node]: '''Returns nodes that also need tagging if 'node' is tagged, but does not recurse. So, if 'node' necessitates tagging node A, and node A necessitates tagging node B, then also_needs_tagging1 will return node A but not node B.''' if isinstance(node, CellRef): yield from fm.painters_of(node, node.value) elif isinstance(node, Painter): yield from fm.behalf_of(node)
def test_codelet_replace_refs(self) -> None: fm = FARGModel() co0 = DummyCodelet(late_bound=Ref('agstr')) ag = DummyAgent(agstr='FROM AGENT', born=DummyCodelet()) co1 = co0.replace_refs(fm, ag) self.assertEqual(co1, DummyCodelet(late_bound='FROM AGENT'))
self, fm: FARGModel, late_bound: str): DummyCodelet.depository = late_bound @dataclass(frozen=True) class DummyAgent(Agent): agstr: str = 'default' # string in Agent; will be copied to DummyAgent.late_bound def short(self) -> str: cl = self.__class__.__name__ return f'{cl}({self.agstr})' ag = DummyAgent(agstr='FROM AGENT', born=DummyCodelet(late_bound=Ref('agstr'))) ag2 = DummyAgent(agstr='ag2', born=DummyCodelet(late_bound=Ref('agstr'))) class TestFARGModel(unittest.TestCase): def test_fargmodel_basics(self) -> None: fm = FARGModel() self.assertEqual(fm.t, 0) # Did it initialize a random-number seed? self.assertIsInstance(fm.seed, int) # Build something ca = fm.build(StepCanvas([Step([4, 5, 6])])) # Is it there? self.assertTrue(fm.the(StepCanvas))