def test_call_returns_empty_numdict_when_no_rules_exist(self): rules = Rules(max_conds=1) 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) self.assertEqual(len(strengths), 0, msg="Unexpected items in output.")
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.")
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.")
interface=alice.assets.wm_interface)) # This terminus controls the agent's speech actions. Construct(name=terminus("speech"), process=ActionSelector( source=features("main"), temperature=.01, interface=alice.assets.speech_interface)) nacs = Structure(name=subsystem("nacs"), assets=Assets(chunks=nacs_cdb)) with nacs: Construct( name=chunks("in"), process=MaxNodes( sources=[buffer("stimulus"), buffer("wm")])) Construct(name=flow_tb("main"), process=TopDown(source=chunks("in"), chunks=nacs.assets.chunks)) Construct( name=features("main"), process=MaxNodes( sources=[buffer("stimulus"), buffer("wm"), flow_tb("main")])) Construct(name=flow_bt("main"),
nacs = Structure(name=subsystem("nacs"), assets=Assets(chunk_db=chunk_db)) with nacs: # Although the entry point for the NACS are chunks, in this example we # start with features as there are no constructs that initially # activate chunks in the NACS activation cycle. Construct(name=features("main"), process=MaxNodes(sources=[buffer("stimulus")])) Construct(name=flow_bt("main"), process=BottomUp(source=features("main"), chunks=nacs.assets.chunk_db)) Construct(name=chunks("main"), process=MaxNodes(sources=[flow_bt("main")])) # Termini # In addition to introducting chunk extraction, this example # demonstrates the use of two temrmini in one single subsytem. We # include one terminus for the output of the top level and one for the # bottom level. # The top level terminus is basically the same as the one used in the # free association example. It randomly selects a chunk through a # competitive process which involves sampling chunks from a boltzmann # distribution constructed from their respective strength values. This # terminus is relevant for the quizzing/querying section of the # simulation.
with nacs: # The first instance of gating in this example is on the stimulus. We # do not gate the stimulus buffer. Instead, we create a flow_in # construct, which repeats the output of the stimulus buffer, and we # gate that. This allows us to gate the stimulus buffer in the NACS # independently from other subsystems. To implement the gate, we # initialize a `Gated` object which wraps the activation repeater. Construct(name=flow_in("stimulus"), process=Gated(base=Repeater(source=buffer("stimulus")), controller=buffer("gate"), interface=gate_interface, pidx=0)) Construct(name=chunks("in"), process=MaxNodes(sources=[flow_in("stimulus")])) Construct(name=flow_tb("main"), process=TopDown(source=chunks("in"), chunks=nacs.assets.cdb)) Construct(name=features("main"), process=MaxNodes(sources=[flow_tb("main")])) Construct(name=flow_tt("associations"), process=Gated(base=AssociativeRules( source=chunks("in"), rules=nacs.assets.rule_db), controller=buffer("gate"), interface=gate_interface, pidx=1))
# chunk and feature pools are used. These pools handle computing the # strengths of chunk and feature nodes in bulk. # This design offers a number of advantages: it is flexible (we do not # need to explicitly declare new chunk or feature nodes to the system), # efficient (all chunk and feature node activations are computed by one # single object in one pass), simple (it reduces bookeeping requirements # when adding and removing nodes) and more intelligible (nodes do not # cause clutter in large models). # One downside to this approach is that we have to be careful about # tracking the feature domain. This is why it is good to define the # (initial) feature domain explicitly prior to agent assembly. Construct( name=chunks("in"), process=MaxNodes(sources=[buffer("stimulus")]) ) # Next up is a top-down activation flow, where activations flow from # chunk nodes to linked feature nodes. Construct( name=flow_tb("main"), process=TopDown( source=chunks("in"), chunks=nacs.assets.cdb ) ) # In this simulation, because there are no bottom-level flows (i.e.,