def test_call_activates_unique_action_and_rule_pair(self): rules = Rules(max_conds=1) rules.define(rule("A"), chunk("Action 1"), chunk("Condition A")) rules.define(rule("B"), chunk("Action 1"), chunk("Condition B")) rules.define(rule("C"), chunk("Action 1"), chunk("Condition C")) rules.define(rule("D"), chunk("Action 2"), chunk("Condition D")) rules.define(rule("E"), chunk("Action 2"), chunk("Condition E")) inputs = { chunks(1): nd.NumDict( { chunk("Condition A"): .7, chunk("Condition B"): .2, chunk("Condition C"): .6, chunk("Condition D"): .5, chunk("Condition E"): .3 }, default=0) } action_rules = ActionRules( source=chunks(1), rules=rules, temperature=1 # high temperature to ensure variety ) strengths = action_rules.call(inputs) above_zero = nd.threshold(strengths, th=0.0) self.assertEqual( len(above_zero), 2, msg="Expected at most two items above zero activation.") if chunk("Action 1") in above_zero: self.assertTrue(rule("A") in above_zero or rule("B") in above_zero or rule("C") in above_zero, msg="Unexpected rule paired with Action 1.") if chunk("Action 2") in above_zero: self.assertTrue(rule("D") in above_zero or rule("E") in above_zero, msg="Unexpected rule paired with Action 2.")
def test_rule_selection_follows_boltzmann_distribution(self): rules = Rules(max_conds=1) rules.define(rule("A"), chunk("Action 1"), chunk("Condition A")) rules.define(rule("B"), chunk("Action 1"), chunk("Condition B")) rules.define(rule("C"), chunk("Action 1"), chunk("Condition C")) rules.define(rule("D"), chunk("Action 2"), chunk("Condition D")) rules.define(rule("E"), chunk("Action 2"), chunk("Condition E")) inputs = { chunks(1): nd.NumDict( { chunk("Condition A"): .7, chunk("Condition B"): .2, chunk("Condition C"): .6, chunk("Condition D"): .5, chunk("Condition E"): .3 }, default=0) } get_rule = lambda c: rule(c.cid[-1]) action_rules = ActionRules( source=chunks(1), rules=rules, temperature=.1 # relatively high temperature to ensure variety ) expected = nd.transform_keys(inputs[chunks(1)], func=get_rule) expected = nd.boltzmann(expected, t=.1) expected = nd.with_default(expected, default=None) N = 100 selected = [] is_rule = lambda sym: sym.ctype in ConstructType.rule for _ in range(N): strengths = action_rules.call(inputs) s = nd.keep(strengths, func=is_rule) s = nd.threshold(s, th=0) s = s.constant(val=1) s = nd.with_default(s, default=0) selected.append(s) counts = nd.ew_sum(*selected) terms = ((counts - (N * expected))**2) / (N * expected) chi_square_stat = nd.val_sum(terms) critical_value = 9.488 # for chi square w/ 4 df @ alpha = .05 self.assertFalse(critical_value < chi_square_stat, msg="Chi square test significant at alpha = .05.")
# mathematical operations. # In this particular simulation, we set our default actions to have a constant # activation of 0.5. default_strengths = nd.MutableNumDict(default=0) default_strengths.extend(gate_interface.defaults, value=0.5) # Next, we initialize and populate chunk and rule databases as in the original # example. cdb = Chunks() rule_db = Rules() rule_db.define( rule("1"), cdb.define(chunk("FRUIT"), feature("tasty", True), feature("sweet", True)), cdb.define(chunk("APPLE"), feature("color", "#ff0000"), feature("color", "#008000"), feature("tasty", True))) cdb.define(chunk("JUICE"), feature("tasty", True), feature("state", "liquid")) ### Agent Assembly ### # The agent assembly process is very similar to `free_association.py`, but we # define some additional constructs and structures. alice = Structure(name=agent("Alice"), assets=Assets(gate_interface=gate_interface)) with alice:
# To house chunk and rule definitions, we initialize a chunk database and a # rule database. cdb = Chunks() rdb = Rules() # We can add rules to the rule database using the `define()` method of the rule # database. The argument signature for `define()` is a rule symbol, followed by # its conclusion chunk and then by one or more condition chunks. Thus, below, # `chunk("FRUIT")` is the conclusion and `chunk("APPLE")` is the only condition. # In other words, this rule establishes an association from the concept APPLE # to the concept FRUIT. In truth, we may also designate condition weights, but # this feature is not explored here. rdb.define(rule(1), chunk("FRUIT"), chunk("APPLE")) # We proceed in much the same way to link chunk and feature nodes in order to # define chunks. # The chunk database has a `define()` method, which can be used to link a chunk # node to feature nodes, creating a fully-formed chunk. The call signature # expects the chunk node first, followed by the feature nodes. By default, # feature notes have a dimensional weight of 1, dimensional weights may be set # explicitly through a keyword argument to `define()`. # The first call to `define()` connects the 'APPLE' chunk node to the red and # green color feature nodes and the tasty feature node. cdb.define( chunk("APPLE"),