From 0b62cd3661251bfb36eae8135f2f7d85415573ef Mon Sep 17 00:00:00 2001 From: Alan Gibson Date: Fri, 23 Feb 2024 13:21:40 -0500 Subject: [PATCH] Updates to system flow Lerners now accept high level data Planners now call update on all existing planner constructs on interface update now call render_reasoning on all planner constructs on interface render_reasoning Complex added update_and_render_reasoning function updates and renders reasoning on each reasoning construct before the next passes results of previous complex reasoners to subsequent complex reasoners removed update and render_reason functions from interface which were no longer used Agent removed render_reasoning it was only called locally and is no longer used it only rendered the complex and that responsibility is now in the complex interface new flow of data through ai constructs vehicle rep updates every knowledge construct with frame (low_level_data) aggregate_high_level_data adds the vehicle reps knowledge construct results (vehicle state information) learning systems all update with frame (low_level_data, vehicle curr data) and aggregate high_level_data aggregate_high_level_data adds the learning systems construct results planning systems all update with aggregate high_level_data aggregate_high_level_data adds the planning systems construct results complex reasoners update and render reasoning (passing results down to each in the interface) with the final result being returned by the agent --- onair/src/ai_components/learners_interface.py | 2 +- onair/src/ai_components/planners_interface.py | 8 +- onair/src/reasoning/agent.py | 19 ++--- .../reasoning/complex_reasoning_interface.py | 13 ++-- .../ai_components/test_learners_interface.py | 4 +- .../ai_components/test_planners_interface.py | 56 ++++++++++++- test/onair/src/reasoning/test_agent.py | 64 ++++++--------- .../test_complex_resoning_interface.py | 78 ++++++------------- 8 files changed, 123 insertions(+), 121 deletions(-) diff --git a/onair/src/ai_components/learners_interface.py b/onair/src/ai_components/learners_interface.py index 63eeed77..07ff0fa9 100644 --- a/onair/src/ai_components/learners_interface.py +++ b/onair/src/ai_components/learners_interface.py @@ -21,7 +21,7 @@ def __init__(self, headers, _learner_plugins={}): def update(self, low_level_data, high_level_data): for plugin in self.learner_constructs: - plugin.update(low_level_data) + plugin.update(low_level_data, high_level_data) def check_for_salient_event(self): pass diff --git a/onair/src/ai_components/planners_interface.py b/onair/src/ai_components/planners_interface.py index 155abb92..54f670fc 100644 --- a/onair/src/ai_components/planners_interface.py +++ b/onair/src/ai_components/planners_interface.py @@ -22,10 +22,14 @@ def __init__(self, headers, _planner_plugins={}): def update(self, high_level_data): # Raw TLM should be transformed into high-leve state representation here # Can store something as stale unless a planning thread is launched - pass + for plugin in self.planner_constructs: + plugin.update(high_level_data=high_level_data) def check_for_salient_event(self): pass def render_reasoning(self): - pass + diagnoses = {} + for plugin in self.planner_constructs: + diagnoses[plugin.component_name] = plugin.render_reasoning() + return diagnoses diff --git a/onair/src/reasoning/agent.py b/onair/src/reasoning/agent.py index f85d3b25..165e7e9c 100644 --- a/onair/src/reasoning/agent.py +++ b/onair/src/reasoning/agent.py @@ -28,21 +28,16 @@ def __init__(self, vehicle, learners_plugin_dict, planners_plugin_dict, complex_ self.planning_systems = PlannersInterface(self.vehicle_rep.get_headers(),planners_plugin_dict) self.complex_reasoning_systems = ComplexReasoningInterface(self.vehicle_rep.get_headers(),complex_plugin_dict) - def render_reasoning(self): - return self.complex_reasoning_systems.render_reasoning() - def reason(self, frame): + aggregate_high_level_info = {} self.vehicle_rep.update(frame) - self.learning_systems.update(self.vehicle_rep.curr_data, self.vehicle_rep.get_state_information()) - self.planning_systems.update(self.vehicle_rep.get_state_information()) - - aggregate_high_level_info = {'vehicle_rep' : self.vehicle_rep.get_state_information(), - 'learning_systems' : self.learning_systems.render_reasoning(), - 'planning_systems' : self.planning_systems.render_reasoning()} - - self.complex_reasoning_systems.update(aggregate_high_level_info) + aggregate_high_level_info['vehicle_rep'] = self.vehicle_rep.get_state_information() + self.learning_systems.update(self.vehicle_rep.curr_data, aggregate_high_level_info) + aggregate_high_level_info['learning_systems'] = self.learning_systems.render_reasoning() + self.planning_systems.update(aggregate_high_level_info) + aggregate_high_level_info['planning_systems'] = self.planning_systems.render_reasoning() - return self.render_reasoning() + return self.complex_reasoning_systems.update_and_render_reasoning(aggregate_high_level_info) def diagnose(self, time_step): """ Grab the mnemonics from the """ diff --git a/onair/src/reasoning/complex_reasoning_interface.py b/onair/src/reasoning/complex_reasoning_interface.py index 4421dc02..2c19d91c 100644 --- a/onair/src/reasoning/complex_reasoning_interface.py +++ b/onair/src/reasoning/complex_reasoning_interface.py @@ -20,15 +20,14 @@ def __init__(self, headers, _reasoning_plugins={}): self.headers = headers self.reasoning_constructs = import_plugins(self.headers,_reasoning_plugins) - def update(self, high_level_data): + def update_and_render_reasoning(self, high_level_data): + intelligent_outcomes = high_level_data + intelligent_outcomes['complex_systems'] = {} for plugin in self.reasoning_constructs: - plugin.update(high_level_data=high_level_data) + plugin.update(high_level_data=intelligent_outcomes) + intelligent_outcomes['complex_systems'] |= {plugin.component_name:plugin.render_reasoning()} + return intelligent_outcomes def check_for_salient_event(self): pass - def render_reasoning(self): - intelligent_outcomes = {} - for plugin in self.reasoning_constructs: - intelligent_outcomes[plugin.component_name] = plugin.render_reasoning() - return intelligent_outcomes diff --git a/test/onair/src/ai_components/test_learners_interface.py b/test/onair/src/ai_components/test_learners_interface.py index 857ea692..e22abf50 100644 --- a/test/onair/src/ai_components/test_learners_interface.py +++ b/test/onair/src/ai_components/test_learners_interface.py @@ -68,7 +68,7 @@ def test_LearnersInterface_update_does_nothing_when_instance_learner_constructs_ # Assert assert result == None -def test_LearnersInterface_update_calls_update_with_given_low_level_data_on_each_learner_constructs_item(mocker): +def test_LearnersInterface_update_calls_update_with_given_low_level_and_high_level_data_on_each_learner_constructs_item(mocker): # Arrange arg_low_level_data = MagicMock() arg_high_level_data = MagicMock() @@ -86,7 +86,7 @@ def test_LearnersInterface_update_calls_update_with_given_low_level_data_on_each # Assert for i in range(num_fake_learner_constructs): assert cut.learner_constructs[i].update.call_count == 1 - assert cut.learner_constructs[i].update.call_args_list[0].args == (arg_low_level_data, ) + assert cut.learner_constructs[i].update.call_args_list[0].args == (arg_low_level_data, arg_high_level_data) # check_for_salient_event def test_LearnersInterface_salient_event_does_nothing(): diff --git a/test/onair/src/ai_components/test_planners_interface.py b/test/onair/src/ai_components/test_planners_interface.py index fd390129..539f7548 100644 --- a/test/onair/src/ai_components/test_planners_interface.py +++ b/test/onair/src/ai_components/test_planners_interface.py @@ -53,11 +53,12 @@ def test_PlannersInterface__init__sets_self_headers_to_given_headers_and_sets_se assert cut.planner_constructs == forced_return_planner_constructs # update tests -def test_update_does_nothing(): +def test_PlannersInterface_update_does_nothing_when_instance_planner_constructs_is_empty(): # Arrange arg_high_level_data = MagicMock() cut = PlannersInterface.__new__(PlannersInterface) + cut.planner_constructs = [] # Act result = cut.update(arg_high_level_data) @@ -65,8 +66,28 @@ def test_update_does_nothing(): # Assert assert result == None +def test_PlannersInterface_update_calls_update_with_given_low_level_and_high_level_data_on_each_planner_constructs_item(mocker): + # Arrange + arg_high_level_data = MagicMock() + + cut = PlannersInterface.__new__(PlannersInterface) + cut.planner_constructs = [] + + num_fake_planner_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) + for i in range(num_fake_planner_constructs): + cut.planner_constructs.append(MagicMock()) + + # Act + result = cut.update(arg_high_level_data) + + # Assert + for i in range(num_fake_planner_constructs): + assert cut.planner_constructs[i].update.call_count == 1 + assert cut.planner_constructs[i].update.call_args_list[0].args == () + assert cut.planner_constructs[i].update.call_args_list[0].kwargs == {'high_level_data':arg_high_level_data} + # check_for_salient_event tests -def test_check_for_salient_event_does_nothing(): +def test_PlannersInterface_check_for_salient_event_does_nothing(): # Arrange cut = PlannersInterface.__new__(PlannersInterface) @@ -77,12 +98,39 @@ def test_check_for_salient_event_does_nothing(): assert result == None # render reasoning tests -def test_render_reasoning_does_nothing(): +def test_PlannersInterface_render_reasoning_returns_empty_dict_when_instance_planner_constructs_is_empty(mocker): # Arrange cut = PlannersInterface.__new__(PlannersInterface) + cut.planner_constructs = [] + + # Act + result = cut.render_reasoning() + + # Assert + assert result == {} + + +def test_PlannersInterface_render_reasoning_returns_dict_of_each_ai_construct_as_key_to_the_result_of_its_render_reasoning_when_instance_planner_constructs_is_occupied(mocker): + # Arrange + cut = PlannersInterface.__new__(PlannersInterface) + cut.planner_constructs = [] + + expected_result = {} + + num_fake_planner_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) + for i in range(num_fake_planner_constructs): + fake_ai_construct = MagicMock() + forced_return_ai_construct_render_reasoning = MagicMock() + cut.planner_constructs.append(fake_ai_construct) + mocker.patch.object(fake_ai_construct, 'render_reasoning', return_value=forced_return_ai_construct_render_reasoning) + fake_ai_construct.component_name = MagicMock() + expected_result[fake_ai_construct.component_name] = forced_return_ai_construct_render_reasoning # Act result = cut.render_reasoning() # Assert - assert result == None \ No newline at end of file + for i in range(num_fake_planner_constructs): + assert cut.planner_constructs[i].render_reasoning.call_count == 1 + assert cut.planner_constructs[i].render_reasoning.call_args_list[0].args == () + assert result == expected_result \ No newline at end of file diff --git a/test/onair/src/reasoning/test_agent.py b/test/onair/src/reasoning/test_agent.py index 3a7eba47..a1eedd88 100644 --- a/test/onair/src/reasoning/test_agent.py +++ b/test/onair/src/reasoning/test_agent.py @@ -62,25 +62,8 @@ def test_Agent__init__sets_vehicle_rep_to_given_vehicle_and_learning_systems_and assert arg_vehicle.get_bayesian_status.call_args_list[0].args == () assert cut.bayesian_status == fake_bayesian_status -# render_resoning tests -def test_Agent_render_reasoning_returns_call_to_complex_reasoning_systems_render_reasoning(mocker): - # Arrange - expected_result = MagicMock() - fake_complex_resoning_systems = MagicMock() - - cut = Agent.__new__(Agent) - cut.complex_reasoning_systems = fake_complex_resoning_systems - - mocker.patch.object(fake_complex_resoning_systems, 'render_reasoning', return_value=expected_result) - - # Act - result = cut.render_reasoning() - - # Assert - assert result == expected_result - # reason tests -def test_Agent_reason_updates_vehicle_rep_with_given_frame_and_updates_learning_systems_with_vehicle_curr_data_and_new_mission_status(mocker): +def test_Agent_reason_updates_vehicle_rep_with_given_frame_learners_with_frame_and_aggregated_high_level_data_planners_with_aggreagated_high_level_data_returning_complex_reasonings_update_and_render_reasoning(mocker): # Arrange arg_frame = MagicMock() fake_vehicle_rep = MagicMock() @@ -88,16 +71,18 @@ def test_Agent_reason_updates_vehicle_rep_with_given_frame_and_updates_learning_ # Mock and patch fake_status = MagicMock() - fake_PDDL_state = MagicMock() - fake_state = MagicMock() + fake_vehicle_rep_state = MagicMock() fake_learning_systems = MagicMock() fake_planning_systems = MagicMock() fake_complex_reasoning_systems = MagicMock() fake_learning_systems_reasoning = MagicMock() fake_planning_systems_reasoning = MagicMock() - expected_aggregate_high_level_info = {'vehicle_rep': fake_state, - 'learning_systems':fake_learning_systems_reasoning, - 'planning_systems':fake_planning_systems_reasoning} + expected_aggregate_to_learners = {'vehicle_rep': fake_vehicle_rep_state} + expected_aggregate_to_planners = {'vehicle_rep': fake_vehicle_rep_state, + 'learning_systems':fake_learning_systems_reasoning} + expected_aggregate_to_complex = {'vehicle_rep': fake_vehicle_rep_state, + 'learning_systems':fake_learning_systems_reasoning, + 'planning_systems':fake_planning_systems_reasoning} expected_result = MagicMock() cut = Agent.__new__(Agent) @@ -109,34 +94,35 @@ def test_Agent_reason_updates_vehicle_rep_with_given_frame_and_updates_learning_ mock_manager = mocker.MagicMock() mock_manager.attach_mock(mocker.patch.object(fake_vehicle_rep, 'update'), 'cut.vehicle_rep.update') + mock_manager.attach_mock(mocker.patch.object(fake_vehicle_rep, 'get_state_information', return_value=fake_vehicle_rep_state), 'cut.vehicle_rep.get_state_information') mock_manager.attach_mock(mocker.patch.object(fake_learning_systems, 'update'), 'cut.learning_systems.update') + mock_manager.attach_mock(mocker.patch.object(fake_learning_systems, 'render_reasoning', return_value=fake_learning_systems_reasoning), 'cut.learning_systems.render_reasoning') mock_manager.attach_mock(mocker.patch.object(fake_planning_systems, 'update'), 'cut.planning_systems.update') - mock_manager.attach_mock(mocker.patch.object(fake_complex_reasoning_systems, 'update'), 'cut.complex_reasoning_systems.update') - mock_manager.attach_mock(mocker.patch.object(cut, 'render_reasoning', return_value=expected_result), 'cut.render_reasoning') + mock_manager.attach_mock(mocker.patch.object(fake_planning_systems, 'render_reasoning', return_value=fake_planning_systems_reasoning), 'cut.planning_systems.render_reasoning') + mock_manager.attach_mock(mocker.patch.object(fake_complex_reasoning_systems, 'update_and_render_reasoning', return_value=expected_result), 'cut.complex_reasoning_systems.update_and_render_reasoning') + - mocker.patch.object(fake_vehicle_rep, 'get_state_information', side_effect=[fake_status, fake_PDDL_state, fake_state]) - mocker.patch.object(fake_learning_systems, 'render_reasoning', return_value=fake_learning_systems_reasoning) - mocker.patch.object(fake_planning_systems, 'render_reasoning', return_value=fake_planning_systems_reasoning) + # mocker.patch.object(fake_learning_systems, 'render_reasoning', return_value=fake_learning_systems_reasoning) + # mocker.patch.object(fake_planning_systems, 'render_reasoning', return_value=fake_planning_systems_reasoning) # Act result = cut.reason(arg_frame) # Assert result = expected_result + #TODO: using expected_aggregate_to_complex is incorrect, appears to maybe be an issue with MagicMock somehow + # problem is its always the same object, that gets updated during the function, unfortunately it only saves the object + # not a "snapshot" of what the object was at the time, so each recorded call thinks it got the object which it did, but the state is wrong + # side_effect could be used to save the true values, but research better options mock_manager.assert_has_calls([ mocker.call.cut.vehicle_rep.update(arg_frame), - mocker.call.cut.learning_systems.update(fake_vehicle_rep.curr_data, fake_status), - mocker.call.cut.planning_systems.update(fake_PDDL_state), - mocker.call.cut.complex_reasoning_systems.update(expected_aggregate_high_level_info), + mocker.call.cut.vehicle_rep.get_state_information(), + mocker.call.cut.learning_systems.update(fake_vehicle_rep.curr_data, expected_aggregate_to_complex), + mocker.call.cut.learning_systems.render_reasoning(), + mocker.call.cut.planning_systems.update(expected_aggregate_to_complex), + mocker.call.cut.planning_systems.render_reasoning(), + mocker.call.cut.complex_reasoning_systems.update_and_render_reasoning(expected_aggregate_to_complex), ], any_order=False) - assert cut.vehicle_rep.get_state_information.call_count == 3 - assert cut.vehicle_rep.get_state_information.call_args_list[0].args == () - assert cut.vehicle_rep.get_state_information.call_args_list[1].args == () - assert cut.vehicle_rep.get_state_information.call_args_list[2].args == () - assert cut.learning_systems.render_reasoning.call_count == 1 - assert cut.learning_systems.render_reasoning.call_args_list[0].args == () - assert cut.planning_systems.render_reasoning.call_count == 1 - assert cut.planning_systems.render_reasoning.call_args_list[0].args == () # diagnose tests diff --git a/test/onair/src/reasoning/test_complex_resoning_interface.py b/test/onair/src/reasoning/test_complex_resoning_interface.py index a67c4245..88e1c675 100644 --- a/test/onair/src/reasoning/test_complex_resoning_interface.py +++ b/test/onair/src/reasoning/test_complex_resoning_interface.py @@ -53,39 +53,46 @@ def test_ComplexReasoningInterface__init__sets_self_headers_to_given_headers_and assert complex_reasoning_interface.import_plugins.call_args_list[0].args == (arg_headers, arg__reasoning_plugins) assert cut.reasoning_constructs == forced_return_reasoning_constructs -# update tests -def test_ComplexReasoningInterface_update_does_nothing_when_instance_reasoning_constructs_is_empty(): +# update_and_render_reasoning +def test_ComplexReasoningInterface_update_and_render_reasoning_returns_given_high_level_data_with_complex_systems_as_empty_dict_when_no_reasoning_constructs(mocker): # Arrange - arg_high_level_data = MagicMock() + fake_high_level_key = MagicMock(name='fake_high_level_key') + fake_high_level_value = MagicMock(name='fake_high_level_value') + arg_high_level_data = {fake_high_level_key:fake_high_level_value} + expected_result = arg_high_level_data | {'complex_systems':{}} cut = ComplexReasoningInterface.__new__(ComplexReasoningInterface) cut.reasoning_constructs = [] # Act - result = cut.update(arg_high_level_data) + result = cut.update_and_render_reasoning(arg_high_level_data) # Assert - assert result == None + assert result == expected_result -def test_ComplexReasoningInterface_update_calls_update_with_given_low_level_data_on_each_reasoning_constructs_item(mocker): +def test_ComplexReasoningInterface_update_and_render_reasoning_invokes_on_all_reasoning_constructs_then_returns_their_results_added_to_the_hgih_level_data(mocker): # Arrange - arg_high_level_data = MagicMock() + fake_high_level_key = MagicMock(name='fake_high_level_key') + fake_high_level_value = MagicMock(name='fake_high_level_value') + arg_high_level_data = {fake_high_level_key:fake_high_level_value} + expected_result = arg_high_level_data | {'complex_systems':{}} cut = ComplexReasoningInterface.__new__(ComplexReasoningInterface) cut.reasoning_constructs = [] - - num_fake_reasoning_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) - for i in range(num_fake_reasoning_constructs): - cut.reasoning_constructs.append(MagicMock()) + for i in range(0, pytest.gen.randint(1, 10)): + cut.reasoning_constructs.append(MagicMock(name=f"fake_plugin_{i}")) + cut.reasoning_constructs[-1].component_name = f"fake_plugin_{i}" + mocker.patch.object(cut.reasoning_constructs[-1], 'update') + rv = f"{i}" + mocker.patch.object(cut.reasoning_constructs[-1], 'render_reasoning', return_value=rv) + expected_result['complex_systems'] |= {cut.reasoning_constructs[-1].component_name : rv} # Act - result = cut.update(arg_high_level_data) + result = cut.update_and_render_reasoning(arg_high_level_data) # Assert - for i in range(num_fake_reasoning_constructs): - assert cut.reasoning_constructs[i].update.call_count == 1 - assert cut.reasoning_constructs[i].update.call_args_list[0].args == () - assert cut.reasoning_constructs[i].update.call_args_list[0].kwargs == {'high_level_data':arg_high_level_data} + assert result == expected_result + assert cut.reasoning_constructs[0].update.call_count == 1 # check_for_salient_event tests def test_ComplexReasoningInterface_salient_event_does_nothing(): @@ -96,41 +103,4 @@ def test_ComplexReasoningInterface_salient_event_does_nothing(): result = cut.check_for_salient_event() # Assert - assert result == None - -# render_reasoning tests -def test_ComplexReasoningInterface_render_reasoning_returns_empty_dict_when_instance_reasoning_constructs_is_empty(mocker): - # Arrange - cut = ComplexReasoningInterface.__new__(ComplexReasoningInterface) - cut.reasoning_constructs = [] - - # Act - result = cut.render_reasoning() - - # Assert - assert result == {} - -def test_ComplexReasoningInterface_render_reasoning_returns_dict_of_each_ai_construct_as_key_to_the_result_of_its_render_reasoning_when_instance_reasoning_constructs_is_occupied(mocker): - # Arrange - cut = ComplexReasoningInterface.__new__(ComplexReasoningInterface) - cut.reasoning_constructs = [] - - expected_result = {} - - num_fake_reasoning_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) - for i in range(num_fake_reasoning_constructs): - fake_ai_construct = MagicMock() - forced_return_ai_construct_render_reasoning = MagicMock() - cut.reasoning_constructs.append(fake_ai_construct) - mocker.patch.object(fake_ai_construct, 'render_reasoning', return_value=forced_return_ai_construct_render_reasoning) - fake_ai_construct.component_name = MagicMock() - expected_result[fake_ai_construct.component_name] = forced_return_ai_construct_render_reasoning - - # Act - result = cut.render_reasoning() - - # Assert - for i in range(num_fake_reasoning_constructs): - assert cut.reasoning_constructs[i].render_reasoning.call_count == 1 - assert cut.reasoning_constructs[i].render_reasoning.call_args_list[0].args == () - assert result == expected_result \ No newline at end of file + assert result == None \ No newline at end of file