Counting¶
In this recipe, we use a “counting” cogent to illustrate basic usage of a few library features:
Elaboration: information, accessible via
IOContainer.e.feature_name, that is computed each cycle (during the DPElaborationphase)create_elaborator: a convenience function for creating anElaboratorbased upon kwargs that associate feature_name with a function to compute the associated value
Termination Check: a function that (during the DP
TerminationCheckphase) determines if the DP should endArguments: name=value pairs supplied to a DP that are accessible via
IOContainer.a.nameLogging: using the standard Python
logginglibraryDebug output: using the
stringifyfunction can improve readability of logs, as well as debug output (e.g., printing a DP)
Detailed Run Summary¶
Let’s focus on line 142, assuming starting_val is 2…
The value
2is accessible via thecounting_startargument (@io.a.counter_start)DP reinit (
state.counter->None); DP run!DP Cycle 1
Elaboration: the union is taken of all the key-value pairs produced across all elaborators (which is just one in this example), yielding
{'is_initialized': False, 'is_perfect': False, 'as_str': 'None'}TerminationCheck: if any check returns
True(including selection of aterminaloperator, by default), the DP stops; but not yet (sincefound_perfectfinds thatio.e.is_perfectisFalse)Propose: the operators are mutually exclusive (by design) and so
initis proposed as the only actionRank: only one action, so
initis selectedApply:
initchangesstate.counter->2
Cycle 2
Elaboration:
io.e->{'is_initialized': True, 'is_perfect': False, 'as_str': '2'}TerminationCheck:
False(still going!)Propose: only
incrementis proposedRank: only one action, so
incrementis selectedApply:
incrementchangesstate.counter->3
Cycle 3
Elaboration:
io.e->{'is_initialized': True, 'is_perfect': False, 'as_str': '3'}TerminationCheck:
False(still going!)Propose: only
incrementis proposedRank: only one action, so
incrementis selectedApply:
incrementchangesstate.counter->4
Cycle 4
Elaboration:
io.e->{'is_initialized': True, 'is_perfect': True, 'as_str': '4'}TerminationCheck:
True(done!)
DP run complete; Cogent gate check (done!)
Arguments (
counting_start) removed
Code¶
1"""
2Illustrates a simple counting agent that
3uses a termination check based upon the
4result of an elaborator
5"""
6
7import logging
8from dataclasses import dataclass
9from math import sqrt
10from typing import cast
11
12from cognition import (
13 Cogent,
14 DecisionProcess,
15 IOContainer,
16 Operator,
17 create_elaborator,
18 stringify,
19)
20
21# ===
22
23# Example of enabling logging to the console (stdout),
24# capturing only those entries that rise to
25# the level of a warning (rare)
26
27handler = logging.StreamHandler() # options exist for files, http, etc.
28handler.setFormatter(
29 logging.Formatter(
30 "%(levelname)s\t%(name)s\t%(asctime)s\t%(message)s"
31 ) # more fields exist
32)
33
34logger = logging.getLogger("cognition") # can be set to a sub-package for focus
35logger.setLevel(logging.WARNING) # set to DEBUG for details
36logger.addHandler(handler)
37
38# ===
39
40
41@dataclass
42class CountingState:
43 """
44 keeps track of where we are in the
45 counting process (init, or current value)
46 """
47
48 counter: int | None = None
49 """
50 current value: None signals to init,
51 otherwise current count
52 """
53
54
55@stringify("is_perfect") # easier-to-read in logs/debug output
56def is_perfect(cs: CountingState, _io: IOContainer) -> bool:
57 """example elaboration: is the counter value a perfect number?"""
58
59 return (cs.counter is not None) and (sqrt(cs.counter) % 1 == 0)
60
61
62# ===
63
64# note that the dp state "initializer" in this
65# case is just the class constructor
66counting_cogent = Cogent(DecisionProcess(CountingState))
67
68# these could very well be methods of the state class,
69# but for illustration, this will make available three
70# keys on io.e.kwarg that automatically update each cycle
71# during the elaboration phase
72counting_cogent.dp.add_elaborator(
73 create_elaborator(
74 "examples", # only visible in logs/debug output
75 is_initialized=lambda cs, _: cs.counter is not None,
76 is_perfect=is_perfect,
77 as_str=lambda cs, _: str(cs.counter), # unused (showing any result type)
78 )
79)
80
81
82@counting_cogent.dp.termination_check
83@stringify("found_perfect")
84def found_perfect(cs: CountingState, io: IOContainer) -> bool:
85 """
86 utilizes elaboration to detect custom termination logic:
87 in this case that a perfect number has been achieved
88 """
89
90 print(f"Terminate?: {cs.counter=} ({io.e.is_perfect=})")
91
92 return cast(bool, io.e.is_perfect)
93
94
95# ===
96
97
98@counting_cogent.dp.operator("init")
99class InitOperator(Operator[CountingState]):
100 """
101 sets the initial counter value
102 based upon a supplied argument
103 """
104
105 def can_perform(self, _state: CountingState, io: IOContainer) -> bool:
106 return not cast(bool, io.e.is_initialized)
107
108 def perform(self, state: CountingState, io: IOContainer) -> None:
109 start_val = cast(int, io.a.counter_start)
110
111 print(f"Initialize: {start_val}")
112
113 state.counter = start_val
114
115
116@counting_cogent.dp.operator("increment")
117class IncrementOperator(Operator[CountingState]):
118 """
119 Increments the counter
120 """
121
122 def can_perform(self, _state: CountingState, io: IOContainer) -> bool:
123 return cast(bool, io.e.is_initialized)
124
125 def perform(self, state: CountingState, _io: IOContainer) -> None:
126 assert state.counter is not None # for type checking
127
128 new_val = state.counter + 1
129 print(f"Increment: {state.counter} -> {new_val}")
130 state.counter = new_val
131
132
133# ===
134
135examples = [1, 2, 5, 11, 101]
136
137for starting_val in examples:
138 print(f"== {starting_val=} ==")
139
140 # when running the agent, kwarg passed to the
141 # agent appear as io.a.kwarg
142 output = cast(int, counting_cogent(counter_start=starting_val).dp.state.counter)
143
144 # print(counting_cogent.dp) # uncomment to see lots of debug details about the DP
145
146 print()
147 print(f"The first perfect square ≥ {starting_val} is {output} ({sqrt(output)=}).")
148 print()