From 26e6de87017e6fa330c45ca9625d45e716c0ecb8 Mon Sep 17 00:00:00 2001 From: Evana Gizzi Date: Thu, 5 Oct 2023 14:58:11 -0400 Subject: [PATCH 01/18] added a planning intrface and renamed the DDL interface to learners. Now we have planners and learners. Closes #50 --- .../__init__.py | 0 .../ai_plugin_abstract/__init__.py | 0 .../ai_plugin_abstract/core.py | 0 .../learners_interface.py} | 2 +- onair/src/ai_components/planners_interface.py | 46 ++++++++++++++++ onair/src/reasoning/agent.py | 4 +- plugins/generic/generic_plugin.py | 2 +- plugins/kalman_plugin/kalman_plugin.py | 2 +- .../ai_plugin_abstract/test_core.py | 4 +- .../test_learners_interface.py} | 52 +++++++++---------- test/onair/src/reasoning/test_agent.py | 6 +-- 11 files changed, 82 insertions(+), 36 deletions(-) rename onair/src/{data_driven_components => ai_components}/__init__.py (100%) rename onair/src/{data_driven_components => ai_components}/ai_plugin_abstract/__init__.py (100%) rename onair/src/{data_driven_components => ai_components}/ai_plugin_abstract/core.py (100%) rename onair/src/{data_driven_components/data_driven_learning.py => ai_components/learners_interface.py} (98%) create mode 100644 onair/src/ai_components/planners_interface.py rename test/onair/src/{data_driven_components => ai_components}/ai_plugin_abstract/test_core.py (96%) rename test/onair/src/{data_driven_components/test_data_driven_learning.py => ai_components/test_learners_interface.py} (70%) diff --git a/onair/src/data_driven_components/__init__.py b/onair/src/ai_components/__init__.py similarity index 100% rename from onair/src/data_driven_components/__init__.py rename to onair/src/ai_components/__init__.py diff --git a/onair/src/data_driven_components/ai_plugin_abstract/__init__.py b/onair/src/ai_components/ai_plugin_abstract/__init__.py similarity index 100% rename from onair/src/data_driven_components/ai_plugin_abstract/__init__.py rename to onair/src/ai_components/ai_plugin_abstract/__init__.py diff --git a/onair/src/data_driven_components/ai_plugin_abstract/core.py b/onair/src/ai_components/ai_plugin_abstract/core.py similarity index 100% rename from onair/src/data_driven_components/ai_plugin_abstract/core.py rename to onair/src/ai_components/ai_plugin_abstract/core.py diff --git a/onair/src/data_driven_components/data_driven_learning.py b/onair/src/ai_components/learners_interface.py similarity index 98% rename from onair/src/data_driven_components/data_driven_learning.py rename to onair/src/ai_components/learners_interface.py index 4a37d491..d6677b33 100644 --- a/onair/src/data_driven_components/data_driven_learning.py +++ b/onair/src/ai_components/learners_interface.py @@ -15,7 +15,7 @@ from ..util.data_conversion import * -class DataDrivenLearning: +class LearnersInterface: def __init__(self, headers, _ai_plugins={}): assert(len(headers)>0), 'Headers are required' self.headers = headers diff --git a/onair/src/ai_components/planners_interface.py b/onair/src/ai_components/planners_interface.py new file mode 100644 index 00000000..80019ee4 --- /dev/null +++ b/onair/src/ai_components/planners_interface.py @@ -0,0 +1,46 @@ +# GSC-19165-1, "The On-Board Artificial Intelligence Research (OnAIR) Platform" +# +# Copyright © 2023 United States Government as represented by the Administrator of +# the National Aeronautics and Space Administration. No copyright is claimed in the +# United States under Title 17, U.S. Code. All Other Rights Reserved. +# +# Licensed under the NASA Open Source Agreement version 1.3 +# See "NOSA GSC-19165-1 OnAIR.pdf" + +""" +Planners interface class for managing all planning-based AI components +""" +import importlib.util +import importlib.util + +from ..util.data_conversion import * + +class PlannersInterface: + def __init__(self, headers, _ai_plugins={}): + assert(len(headers)>0), 'Headers are required' + self.headers = headers + self.planning_constructs = [] + for module_name in list(_planning_plugins.keys()): + spec = importlib.util.spec_from_file_location(module_name, _planning_plugins[module_name]) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + self.planning_constructs.append(module.Plugin(module_name,headers)) + + def update(self, curr_data, status): + input_data = curr_data + output_data = status_to_oneHot(status) + for plugin in self.planning_constructs: + plugin.update(input_data) + + def apriori_training(self, batch_data): + for plugin in self.planning_constructs: + plugin.apriori_training(batch_data) + + def render_reasoning(self): + diagnoses = {} + for plugin in self.planning_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 6a28a16d..83dcde33 100644 --- a/onair/src/reasoning/agent.py +++ b/onair/src/reasoning/agent.py @@ -11,13 +11,13 @@ Agent Class Deals with supervised learning for diagnosing statuses """ -from ..data_driven_components.data_driven_learning import DataDrivenLearning +from ..ai_components.learners_interface import LearnersInterface from ..reasoning.diagnosis import Diagnosis class Agent: def __init__(self, vehicle, plugin_list): self.vehicle_rep = vehicle - self.learning_systems = DataDrivenLearning(self.vehicle_rep.get_headers(),plugin_list) + self.learning_systems = LearnersInterface(self.vehicle_rep.get_headers(),plugin_list) self.mission_status = self.vehicle_rep.get_status() self.bayesian_status = self.vehicle_rep.get_bayesian_status() diff --git a/plugins/generic/generic_plugin.py b/plugins/generic/generic_plugin.py index 1b0da3ae..6a8a9acd 100644 --- a/plugins/generic/generic_plugin.py +++ b/plugins/generic/generic_plugin.py @@ -8,7 +8,7 @@ # See "NOSA GSC-19165-1 OnAIR.pdf" import numpy as np -from onair.src.data_driven_components.ai_plugin_abstract.core import AIPlugIn +from onair.src.ai_components.ai_plugin_abstract.core import AIPlugIn class Plugin(AIPlugIn): def apriori_training(self,batch_data=[]): diff --git a/plugins/kalman_plugin/kalman_plugin.py b/plugins/kalman_plugin/kalman_plugin.py index 7a557361..f97bb62a 100644 --- a/plugins/kalman_plugin/kalman_plugin.py +++ b/plugins/kalman_plugin/kalman_plugin.py @@ -9,7 +9,7 @@ import simdkalman import numpy as np -from onair.src.data_driven_components.ai_plugin_abstract.core import AIPlugIn +from onair.src.ai_components.ai_plugin_abstract.core import AIPlugIn class Plugin(AIPlugIn): def __init__(self, name, headers, window_size=3): diff --git a/test/onair/src/data_driven_components/ai_plugin_abstract/test_core.py b/test/onair/src/ai_components/ai_plugin_abstract/test_core.py similarity index 96% rename from test/onair/src/data_driven_components/ai_plugin_abstract/test_core.py rename to test/onair/src/ai_components/ai_plugin_abstract/test_core.py index 7667b5fb..ec7eb6ea 100644 --- a/test/onair/src/data_driven_components/ai_plugin_abstract/test_core.py +++ b/test/onair/src/ai_components/ai_plugin_abstract/test_core.py @@ -11,8 +11,8 @@ import pytest from mock import MagicMock -import onair.src.data_driven_components.ai_plugin_abstract.core as core -from onair.src.data_driven_components.ai_plugin_abstract.core import AIPlugIn +import onair.src.ai_components.ai_plugin_abstract.core as core +from onair.src.ai_components.ai_plugin_abstract.core import AIPlugIn class FakeAIPlugIn(AIPlugIn): def __init__(self, _name, _headers): diff --git a/test/onair/src/data_driven_components/test_data_driven_learning.py b/test/onair/src/ai_components/test_learners_interface.py similarity index 70% rename from test/onair/src/data_driven_components/test_data_driven_learning.py rename to test/onair/src/ai_components/test_learners_interface.py index e39d89e1..9bd737e4 100644 --- a/test/onair/src/data_driven_components/test_data_driven_learning.py +++ b/test/onair/src/ai_components/test_learners_interface.py @@ -7,18 +7,18 @@ # Licensed under the NASA Open Source Agreement version 1.3 # See "NOSA GSC-19165-1 OnAIR.pdf" -""" Test DataDrivenLearning Functionality """ +""" Test LearnersInterface Functionality """ import pytest from mock import MagicMock -import onair.src.data_driven_components.data_driven_learning as data_driven_learning -from onair.src.data_driven_components.data_driven_learning import DataDrivenLearning +import onair.src.ai_components.learners_interface as learners_interface +from onair.src.ai_components.learners_interface import LearnersInterface import importlib from typing import Dict # __init__ tests -def test_DataDrivenLearning__init__sets_instance_headers_to_given_headers_and_does_nothing_else_when_given__ai_plugins_is_empty(mocker): +def test_LearnersInterface__init__sets_instance_headers_to_given_headers_and_does_nothing_else_when_given__ai_plugins_is_empty(mocker): # Arrange arg_headers = [] arg__ai_plugins = {} @@ -27,7 +27,7 @@ def test_DataDrivenLearning__init__sets_instance_headers_to_given_headers_and_do for i in range(num_fake_headers): arg_headers.append(MagicMock()) - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) # Act cut.__init__(arg_headers, arg__ai_plugins) @@ -35,7 +35,7 @@ def test_DataDrivenLearning__init__sets_instance_headers_to_given_headers_and_do # Assert assert cut.headers == arg_headers -def test_DataDrivenLearning__init__throws_AttributeError_when_given_module_file_has_no_attribute_Plugin(mocker): +def test_LearnersInterface__init__throws_AttributeError_when_given_module_file_has_no_attribute_Plugin(mocker): # Arrange fake_module_name = MagicMock() arg_headers = [] @@ -48,7 +48,7 @@ def test_DataDrivenLearning__init__throws_AttributeError_when_given_module_file_ # Assert -def test_DataDrivenLearning__init__sets_instance_ai_constructs_to_a_list_of_the_calls_AIPlugIn_with_plugin_and_given_headers_for_each_item_in_given__ai_plugins_when_given__ai_plugins_is_occupied(mocker): +def test_LearnersInterface__init__sets_instance_ai_constructs_to_a_list_of_the_calls_AIPlugIn_with_plugin_and_given_headers_for_each_item_in_given__ai_plugins_when_given__ai_plugins_is_occupied(mocker): # Arrange arg_headers = [] arg__ai_plugins = {} @@ -77,7 +77,7 @@ def test_DataDrivenLearning__init__sets_instance_ai_constructs_to_a_list_of_the_ for i, module in enumerate(fake_module_list): mocker.patch.object(module,'Plugin',return_value=expected_ai_constructs[i]) - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) # Act cut.__init__(arg_headers, arg__ai_plugins) @@ -99,32 +99,32 @@ def test_DataDrivenLearning__init__sets_instance_ai_constructs_to_a_list_of_the_ assert cut.ai_constructs == expected_ai_constructs # update tests -def test_DataDrivenLearning_update_only_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_when_instance_ai_constructs_is_empty(mocker): +def test_LearnersInterface_update_only_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_when_instance_ai_constructs_is_empty(mocker): # Arrange arg_curr_data = MagicMock() arg_status = MagicMock() - mocker.patch(data_driven_learning.__name__ + '.status_to_oneHot') + mocker.patch(learners_interface.__name__ + '.status_to_oneHot') - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) cut.ai_constructs = [] # Act result = cut.update(arg_curr_data, arg_status) # Assert - assert data_driven_learning.status_to_oneHot.call_count == 1 - assert data_driven_learning.status_to_oneHot.call_args_list[0].args == (arg_status,) + assert learners_interface.status_to_oneHot.call_count == 1 + assert learners_interface.status_to_oneHot.call_args_list[0].args == (arg_status,) assert result == None -def test_DataDrivenLearning_update_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_and_calls_update_on_each_ai_construct_with_input_data_when_instance_ai_constructs_is_occupied(mocker): +def test_LearnersInterface_update_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_and_calls_update_on_each_ai_construct_with_input_data_when_instance_ai_constructs_is_occupied(mocker): # Arrange arg_curr_data = MagicMock() arg_status = MagicMock() - mocker.patch(data_driven_learning.__name__ + '.status_to_oneHot') + mocker.patch(learners_interface.__name__ + '.status_to_oneHot') - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) cut.ai_constructs = [] num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) @@ -137,19 +137,19 @@ def test_DataDrivenLearning_update_calls_flotify_input_with_given_curr_data_and_ result = cut.update(arg_curr_data, arg_status) # Assert - assert data_driven_learning.status_to_oneHot.call_count == 1 - assert data_driven_learning.status_to_oneHot.call_args_list[0].args == (arg_status,) + assert learners_interface.status_to_oneHot.call_count == 1 + assert learners_interface.status_to_oneHot.call_args_list[0].args == (arg_status,) for i in range(num_fake_ai_constructs): assert cut.ai_constructs[i].update.call_count == 1 assert cut.ai_constructs[i].update.call_args_list[0].args == (arg_curr_data,) assert result == None # apriori_training tests -def test_DataDrivenLearning_apriori_training_does_nothing_when_instance_ai_constructs_is_empty(): +def test_LearnersInterface_apriori_training_does_nothing_when_instance_ai_constructs_is_empty(): # Arrange arg_batch_data = MagicMock() - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) cut.ai_constructs = [] # Act @@ -158,11 +158,11 @@ def test_DataDrivenLearning_apriori_training_does_nothing_when_instance_ai_const # Assert assert result == None -def test_DataDrivenLearning_apriori_training_calls_apriori_training_on_each_ai_constructs_item(mocker): +def test_LearnersInterface_apriori_training_calls_apriori_training_on_each_ai_constructs_item(mocker): # Arrange arg_batch_data = MagicMock() - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) cut.ai_constructs = [] num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) @@ -179,9 +179,9 @@ def test_DataDrivenLearning_apriori_training_calls_apriori_training_on_each_ai_c assert result == None # render_reasoning tests -def test_DataDrivenLearning_render_reasoning_returns_empty_dict_when_instance_ai_constructs_is_empty(mocker): +def test_LearnersInterface_render_reasoning_returns_empty_dict_when_instance_ai_constructs_is_empty(mocker): # Arrange - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) cut.ai_constructs = [] # Act @@ -190,9 +190,9 @@ def test_DataDrivenLearning_render_reasoning_returns_empty_dict_when_instance_ai # Assert assert result == {} -def test_DataDrivenLearning_render_reasoning_returns_dict_of_each_ai_construct_as_key_to_the_result_of_its_render_reasoning_when_instance_ai_constructs_is_occupied(mocker): +def test_LearnersInterface_render_reasoning_returns_dict_of_each_ai_construct_as_key_to_the_result_of_its_render_reasoning_when_instance_ai_constructs_is_occupied(mocker): # Arrange - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) cut.ai_constructs = [] expected_result = {} diff --git a/test/onair/src/reasoning/test_agent.py b/test/onair/src/reasoning/test_agent.py index 5713ea06..cef204ce 100644 --- a/test/onair/src/reasoning/test_agent.py +++ b/test/onair/src/reasoning/test_agent.py @@ -26,7 +26,7 @@ def test_Agent__init__sets_vehicle_rep_to_given_vehicle_and_learning_systems_and fake_plugin_list = MagicMock() mocker.patch.object(arg_vehicle, 'get_headers', return_value=fake_headers) - mocker.patch(agent.__name__ + '.DataDrivenLearning', return_value=fake_learning_systems) + mocker.patch(agent.__name__ + '.LearnersInterface', return_value=fake_learning_systems) mocker.patch.object(arg_vehicle, 'get_status', return_value=fake_mission_status) mocker.patch.object(arg_vehicle, 'get_bayesian_status', return_value=fake_bayesian_status) @@ -39,8 +39,8 @@ def test_Agent__init__sets_vehicle_rep_to_given_vehicle_and_learning_systems_and assert cut.vehicle_rep == arg_vehicle assert arg_vehicle.get_headers.call_count == 1 assert arg_vehicle.get_headers.call_args_list[0].args == () - assert agent.DataDrivenLearning.call_count == 1 - assert agent.DataDrivenLearning.call_args_list[0].args == (fake_headers, fake_plugin_list) + assert agent.LearnersInterface.call_count == 1 + assert agent.LearnersInterface.call_args_list[0].args == (fake_headers, fake_plugin_list) assert cut.learning_systems == fake_learning_systems assert arg_vehicle.get_status.call_count == 1 assert arg_vehicle.get_status.call_args_list[0].args == () From ced65ec5a53ab6a3ccbd5f25e438c909298f2f42 Mon Sep 17 00:00:00 2001 From: Evana Gizzi Date: Thu, 5 Oct 2023 21:29:44 -0400 Subject: [PATCH 02/18] Needed to add a test_planner_interface suite --- onair/src/ai_components/planners_interface.py | 14 +- onair/src/reasoning/agent.py | 7 +- .../ai_components/test_planners_interface.py | 214 ++++++++++++++++++ test/onair/src/reasoning/test_agent.py | 11 +- 4 files changed, 237 insertions(+), 9 deletions(-) create mode 100644 test/onair/src/ai_components/test_planners_interface.py diff --git a/onair/src/ai_components/planners_interface.py b/onair/src/ai_components/planners_interface.py index 80019ee4..78a1666e 100644 --- a/onair/src/ai_components/planners_interface.py +++ b/onair/src/ai_components/planners_interface.py @@ -19,26 +19,26 @@ class PlannersInterface: def __init__(self, headers, _ai_plugins={}): assert(len(headers)>0), 'Headers are required' self.headers = headers - self.planning_constructs = [] - for module_name in list(_planning_plugins.keys()): - spec = importlib.util.spec_from_file_location(module_name, _planning_plugins[module_name]) + self.ai_constructs = [] + for module_name in list(_ai_plugins.keys()): + spec = importlib.util.spec_from_file_location(module_name, _ai_plugins[module_name]) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) - self.planning_constructs.append(module.Plugin(module_name,headers)) + self.ai_constructs.append(module.Plugin(module_name,headers)) def update(self, curr_data, status): input_data = curr_data output_data = status_to_oneHot(status) - for plugin in self.planning_constructs: + for plugin in self.ai_constructs: plugin.update(input_data) def apriori_training(self, batch_data): - for plugin in self.planning_constructs: + for plugin in self.ai_constructs: plugin.apriori_training(batch_data) def render_reasoning(self): diagnoses = {} - for plugin in self.planning_constructs: + for plugin in self.ai_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 83dcde33..2b827ced 100644 --- a/onair/src/reasoning/agent.py +++ b/onair/src/reasoning/agent.py @@ -12,20 +12,25 @@ Deals with supervised learning for diagnosing statuses """ from ..ai_components.learners_interface import LearnersInterface +from ..ai_components.planners_interface import PlannersInterface from ..reasoning.diagnosis import Diagnosis class Agent: def __init__(self, vehicle, plugin_list): self.vehicle_rep = vehicle - self.learning_systems = LearnersInterface(self.vehicle_rep.get_headers(),plugin_list) self.mission_status = self.vehicle_rep.get_status() self.bayesian_status = self.vehicle_rep.get_bayesian_status() + # AI Interfaces + self.learning_systems = LearnersInterface(self.vehicle_rep.get_headers(),plugin_list) + self.planning_systems = PlannersInterface(self.vehicle_rep.get_headers(),plugin_list) + # Markov Assumption holds def reason(self, frame): self.vehicle_rep.update(frame) self.mission_status = self.vehicle_rep.get_status() self.learning_systems.update(frame, self.mission_status) + self.planning_systems.update(frame, self.mission_status) def diagnose(self, time_step): """ Grab the mnemonics from the """ diff --git a/test/onair/src/ai_components/test_planners_interface.py b/test/onair/src/ai_components/test_planners_interface.py new file mode 100644 index 00000000..bae6fc7d --- /dev/null +++ b/test/onair/src/ai_components/test_planners_interface.py @@ -0,0 +1,214 @@ +# GSC-19165-1, "The On-Board Artificial Intelligence Research (OnAIR) Platform" +# +# Copyright © 2023 United States Government as represented by the Administrator of +# the National Aeronautics and Space Administration. No copyright is claimed in the +# United States under Title 17, U.S. Code. All Other Rights Reserved. +# +# Licensed under the NASA Open Source Agreement version 1.3 +# See "NOSA GSC-19165-1 OnAIR.pdf" + +""" Test PlannersInterface Functionality """ +import pytest +from mock import MagicMock + +import onair.src.ai_components.planners_interface as planners_interface +from onair.src.ai_components.planners_interface import PlannersInterface + +import importlib +from typing import Dict + +# __init__ tests +def test_PlannersInterface__init__sets_instance_headers_to_given_headers_and_does_nothing_else_when_given__ai_plugins_is_empty(mocker): + # Arrange + arg_headers = [] + arg__ai_plugins = {} + + num_fake_headers = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 headers (0 has own test) + for i in range(num_fake_headers): + arg_headers.append(MagicMock()) + + cut = PlannersInterface.__new__(PlannersInterface) + + # Act + cut.__init__(arg_headers, arg__ai_plugins) + + # Assert + assert cut.headers == arg_headers + +def test_PlannersInterface__init__throws_AttributeError_when_given_module_file_has_no_attribute_Plugin(mocker): + # Arrange + fake_module_name = MagicMock() + arg_headers = [] + + arg__ai_plugins = {MagicMock()} + + # Act + + # Assert + +def test_PlannersInterfacee__init__sets_instance_ai_constructs_to_a_list_of_the_calls_AIPlugIn_with_plugin_and_given_headers_for_each_item_in_given__ai_plugins_when_given__ai_plugins_is_occupied(mocker): + # Arrange + arg_headers = [] + arg__ai_plugins = {} + fake_spec_list = [] + fake_module_list = [] + + num_fake_headers = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 headers (0 has own test) + for i in range(num_fake_headers): + arg_headers.append(MagicMock()) + num_fake_ai_plugins = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) + for i in range(num_fake_ai_plugins): + arg__ai_plugins[str(MagicMock())] = str(MagicMock()) + fake_spec_list.append(MagicMock()) + fake_module_list.append(MagicMock()) + + + expected_ai_constructs = [] + for i in range(num_fake_ai_plugins): + expected_ai_constructs.append(MagicMock()) + + # mocker.patch('importlib.import_module', return_value=fake_imported_module) + mocker.patch('importlib.util.spec_from_file_location',side_effect=fake_spec_list) + mocker.patch('importlib.util.module_from_spec',side_effect=fake_module_list) + for spec in fake_spec_list: + mocker.patch.object(spec,'loader.exec_module') + for i, module in enumerate(fake_module_list): + mocker.patch.object(module,'Plugin',return_value=expected_ai_constructs[i]) + + cut = PlannersInterface.__new__(PlannersInterface) + + # Act + cut.__init__(arg_headers, arg__ai_plugins) + + # Assert + assert importlib.util.spec_from_file_location.call_count == len(arg__ai_plugins) + assert importlib.util.module_from_spec.call_count == len(fake_spec_list) + + for i in range(num_fake_ai_plugins): + fake_name = list(arg__ai_plugins.keys())[i] + fake_path = arg__ai_plugins[fake_name] + assert importlib.util.spec_from_file_location.call_args_list[i].args == (fake_name,fake_path) + assert importlib.util.module_from_spec.call_args_list[i].args == (fake_spec_list[i],) + assert fake_spec_list[i].loader.exec_module.call_count == 1 + assert fake_spec_list[i].loader.exec_module.call_args_list[0].args == (fake_module_list[i],) + assert fake_module_list[i].Plugin.call_count == 1 + assert fake_module_list[i].Plugin.call_args_list[0].args == (fake_name,arg_headers) + + assert cut.ai_constructs == expected_ai_constructs + +# update tests +def test_PlannersInterface_update_only_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_when_instance_ai_constructs_is_empty(mocker): + # Arrange + arg_curr_data = MagicMock() + arg_status = MagicMock() + + mocker.patch(planners_interface.__name__ + '.status_to_oneHot') + + cut = PlannersInterface.__new__(PlannersInterface) + cut.ai_constructs = [] + + # Act + result = cut.update(arg_curr_data, arg_status) + + # Assert + assert planners_interface.status_to_oneHot.call_count == 1 + assert planners_interface.status_to_oneHot.call_args_list[0].args == (arg_status,) + assert result == None + +def test_PlannersInterface_update_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_and_calls_update_on_each_ai_construct_with_input_data_when_instance_ai_constructs_is_occupied(mocker): + # Arrange + arg_curr_data = MagicMock() + arg_status = MagicMock() + + mocker.patch(planners_interface.__name__ + '.status_to_oneHot') + + cut = PlannersInterface.__new__(PlannersInterface) + cut.ai_constructs = [] + + num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) + for i in range(num_fake_ai_constructs): + fake_ai_construct = MagicMock() + cut.ai_constructs.append(fake_ai_construct) + mocker.patch.object(fake_ai_construct, 'update') + + # Act + result = cut.update(arg_curr_data, arg_status) + + # Assert + assert planners_interface.status_to_oneHot.call_count == 1 + assert planners_interface.status_to_oneHot.call_args_list[0].args == (arg_status,) + for i in range(num_fake_ai_constructs): + assert cut.ai_constructs[i].update.call_count == 1 + assert cut.ai_constructs[i].update.call_args_list[0].args == (arg_curr_data,) + assert result == None + +# apriori_training tests +def test_PlannersInterface_apriori_training_does_nothing_when_instance_ai_constructs_is_empty(): + # Arrange + arg_batch_data = MagicMock() + + cut = PlannersInterface.__new__(PlannersInterface) + cut.ai_constructs = [] + + # Act + result = cut.apriori_training(arg_batch_data) + + # Assert + assert result == None + +def test_PlannersInterface_apriori_training_calls_apriori_training_on_each_ai_constructs_item(mocker): + # Arrange + arg_batch_data = MagicMock() + + cut = PlannersInterface.__new__(PlannersInterface) + cut.ai_constructs = [] + + num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) + for i in range(num_fake_ai_constructs): + cut.ai_constructs.append(MagicMock()) + + # Act + result = cut.apriori_training(arg_batch_data) + + # Assert + for i in range(num_fake_ai_constructs): + assert cut.ai_constructs[i].apriori_training.call_count == 1 + assert cut.ai_constructs[i].apriori_training.call_args_list[0].args == (arg_batch_data, ) + assert result == None + +# render_reasoning tests +def test_PlannersInterface_render_reasoning_returns_empty_dict_when_instance_ai_constructs_is_empty(mocker): + # Arrange + cut = PlannersInterface.__new__(PlannersInterface) + cut.ai_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_ai_constructs_is_occupied(mocker): + # Arrange + cut = PlannersInterface.__new__(PlannersInterface) + cut.ai_constructs = [] + + expected_result = {} + + num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) + for i in range(num_fake_ai_constructs): + fake_ai_construct = MagicMock() + forced_return_ai_construct_render_reasoning = MagicMock() + cut.ai_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_ai_constructs): + assert cut.ai_constructs[i].render_reasoning.call_count == 1 + assert cut.ai_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 cef204ce..65c37aa2 100644 --- a/test/onair/src/reasoning/test_agent.py +++ b/test/onair/src/reasoning/test_agent.py @@ -21,12 +21,14 @@ def test_Agent__init__sets_vehicle_rep_to_given_vehicle_and_learning_systems_and fake_headers = MagicMock() fake_learning_systems = MagicMock() + fake_planning_systems = MagicMock() fake_mission_status = MagicMock() fake_bayesian_status = MagicMock() fake_plugin_list = MagicMock() mocker.patch.object(arg_vehicle, 'get_headers', return_value=fake_headers) mocker.patch(agent.__name__ + '.LearnersInterface', return_value=fake_learning_systems) + mocker.patch(agent.__name__ + '.PlannersInterface', return_value=fake_planning_systems) mocker.patch.object(arg_vehicle, 'get_status', return_value=fake_mission_status) mocker.patch.object(arg_vehicle, 'get_bayesian_status', return_value=fake_bayesian_status) @@ -37,11 +39,14 @@ def test_Agent__init__sets_vehicle_rep_to_given_vehicle_and_learning_systems_and # Assert assert cut.vehicle_rep == arg_vehicle - assert arg_vehicle.get_headers.call_count == 1 + assert arg_vehicle.get_headers.call_count == 2 assert arg_vehicle.get_headers.call_args_list[0].args == () assert agent.LearnersInterface.call_count == 1 assert agent.LearnersInterface.call_args_list[0].args == (fake_headers, fake_plugin_list) assert cut.learning_systems == fake_learning_systems + assert agent.PlannersInterface.call_count == 1 + assert agent.PlannersInterface.call_args_list[0].args == (fake_headers, fake_plugin_list) + assert cut.planning_systems == fake_planning_systems assert arg_vehicle.get_status.call_count == 1 assert arg_vehicle.get_status.call_args_list[0].args == () assert cut.mission_status == fake_mission_status @@ -59,6 +64,7 @@ def test_Agent_reason_updates_vehicle_rep_with_given_frame_and_sets_new_vehicle_ cut = Agent.__new__(Agent) cut.vehicle_rep = MagicMock() cut.learning_systems = MagicMock() + cut.planning_systems = MagicMock() mocker.patch.object(cut.vehicle_rep, 'update') mocker.patch.object(cut.vehicle_rep, 'get_status', return_value=fake_mission_status) @@ -74,6 +80,8 @@ def test_Agent_reason_updates_vehicle_rep_with_given_frame_and_sets_new_vehicle_ assert cut.vehicle_rep.get_status.call_args_list[0].args == () assert cut.learning_systems.update.call_count == 1 assert cut.learning_systems.update.call_args_list[0].args == (arg_frame, fake_mission_status) + assert cut.planning_systems.update.call_count == 1 + assert cut.planning_systems.update.call_args_list[0].args == (arg_frame, fake_mission_status) # diagnose tests def test_Agent_diagnose_returns_empty_Dict(): @@ -82,6 +90,7 @@ def test_Agent_diagnose_returns_empty_Dict(): cut = Agent.__new__(Agent) cut.learning_systems = MagicMock() + cut.planning_systems = MagicMock() cut.bayesian_status = MagicMock() cut.vehicle_rep = MagicMock() From 3bfd5ee10171bd6f07a0aefb40055a3b42236524 Mon Sep 17 00:00:00 2001 From: Evana Gizzi Date: Thu, 5 Oct 2023 21:31:58 -0400 Subject: [PATCH 03/18] removed extra lines and double import --- onair/src/ai_components/learners_interface.py | 4 ---- onair/src/ai_components/planners_interface.py | 4 ---- 2 files changed, 8 deletions(-) diff --git a/onair/src/ai_components/learners_interface.py b/onair/src/ai_components/learners_interface.py index d6677b33..76f4058b 100644 --- a/onair/src/ai_components/learners_interface.py +++ b/onair/src/ai_components/learners_interface.py @@ -11,7 +11,6 @@ Data driven learning class for managing all data driven AI components """ import importlib.util -import importlib.util from ..util.data_conversion import * @@ -41,6 +40,3 @@ def render_reasoning(self): for plugin in self.ai_constructs: diagnoses[plugin.component_name] = plugin.render_reasoning() return diagnoses - - - diff --git a/onair/src/ai_components/planners_interface.py b/onair/src/ai_components/planners_interface.py index 78a1666e..7de0483c 100644 --- a/onair/src/ai_components/planners_interface.py +++ b/onair/src/ai_components/planners_interface.py @@ -11,7 +11,6 @@ Planners interface class for managing all planning-based AI components """ import importlib.util -import importlib.util from ..util.data_conversion import * @@ -41,6 +40,3 @@ def render_reasoning(self): for plugin in self.ai_constructs: diagnoses[plugin.component_name] = plugin.render_reasoning() return diagnoses - - - From dcbb457d012b4bc858603671da64b626ff19b13b Mon Sep 17 00:00:00 2001 From: Evana Gizzi Date: Thu, 5 Oct 2023 21:43:40 -0400 Subject: [PATCH 04/18] Changed the planner interface a bit in order to handle the disambiguation of planners versus learners --- onair/src/ai_components/learners_interface.py | 9 ++++++--- onair/src/ai_components/planners_interface.py | 19 +++++++++---------- onair/src/reasoning/agent.py | 5 +++++ 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/onair/src/ai_components/learners_interface.py b/onair/src/ai_components/learners_interface.py index 76f4058b..b07c2327 100644 --- a/onair/src/ai_components/learners_interface.py +++ b/onair/src/ai_components/learners_interface.py @@ -25,15 +25,18 @@ def __init__(self, headers, _ai_plugins={}): spec.loader.exec_module(module) self.ai_constructs.append(module.Plugin(module_name,headers)) + def apriori_training(self, batch_data): + for plugin in self.ai_constructs: + plugin.apriori_training(batch_data) + def update(self, curr_data, status): input_data = curr_data output_data = status_to_oneHot(status) for plugin in self.ai_constructs: plugin.update(input_data) - def apriori_training(self, batch_data): - for plugin in self.ai_constructs: - plugin.apriori_training(batch_data) + def check_for_salient_event(self): + pass def render_reasoning(self): diagnoses = {} diff --git a/onair/src/ai_components/planners_interface.py b/onair/src/ai_components/planners_interface.py index 7de0483c..bc9a366d 100644 --- a/onair/src/ai_components/planners_interface.py +++ b/onair/src/ai_components/planners_interface.py @@ -25,18 +25,17 @@ def __init__(self, headers, _ai_plugins={}): spec.loader.exec_module(module) self.ai_constructs.append(module.Plugin(module_name,headers)) - def update(self, curr_data, status): - input_data = curr_data - output_data = status_to_oneHot(status) - for plugin in self.ai_constructs: - plugin.update(input_data) - def apriori_training(self, batch_data): for plugin in self.ai_constructs: plugin.apriori_training(batch_data) + def update(self, curr_raw_tlm): + # Raw TLM should be transformed into high-leve state representation here + # Can store something as stale unless a planning thread is launched + pass + + def check_for_salient_event(self): + pass + def render_reasoning(self): - diagnoses = {} - for plugin in self.ai_constructs: - diagnoses[plugin.component_name] = plugin.render_reasoning() - return diagnoses + pass \ No newline at end of file diff --git a/onair/src/reasoning/agent.py b/onair/src/reasoning/agent.py index 2b827ced..9b83a5ee 100644 --- a/onair/src/reasoning/agent.py +++ b/onair/src/reasoning/agent.py @@ -27,11 +27,16 @@ def __init__(self, vehicle, plugin_list): # Markov Assumption holds def reason(self, frame): + # Update with new telemetry self.vehicle_rep.update(frame) self.mission_status = self.vehicle_rep.get_status() self.learning_systems.update(frame, self.mission_status) self.planning_systems.update(frame, self.mission_status) + # Check for a salient event, needing acionable outcome + self.learning_systems.check_for_salient_event() + self.planning_systems.check_for_salient_event() + def diagnose(self, time_step): """ Grab the mnemonics from the """ learning_system_results = self.learning_systems.render_reasoning() From 7fc6c23735b305bd2375b920136321d0b6f922cc Mon Sep 17 00:00:00 2001 From: Evana Gizzi Date: Thu, 5 Oct 2023 21:55:11 -0400 Subject: [PATCH 05/18] updated tests --- onair/src/ai_components/planners_interface.py | 8 +- .../ai_components/test_planners_interface.py | 115 ------------------ 2 files changed, 2 insertions(+), 121 deletions(-) diff --git a/onair/src/ai_components/planners_interface.py b/onair/src/ai_components/planners_interface.py index bc9a366d..f8339b86 100644 --- a/onair/src/ai_components/planners_interface.py +++ b/onair/src/ai_components/planners_interface.py @@ -24,12 +24,8 @@ def __init__(self, headers, _ai_plugins={}): module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) self.ai_constructs.append(module.Plugin(module_name,headers)) - - def apriori_training(self, batch_data): - for plugin in self.ai_constructs: - plugin.apriori_training(batch_data) - def update(self, curr_raw_tlm): + def update(self, curr_raw_tlm, status): # Raw TLM should be transformed into high-leve state representation here # Can store something as stale unless a planning thread is launched pass @@ -38,4 +34,4 @@ def check_for_salient_event(self): pass def render_reasoning(self): - pass \ No newline at end of file + pass diff --git a/test/onair/src/ai_components/test_planners_interface.py b/test/onair/src/ai_components/test_planners_interface.py index bae6fc7d..032b9b86 100644 --- a/test/onair/src/ai_components/test_planners_interface.py +++ b/test/onair/src/ai_components/test_planners_interface.py @@ -96,119 +96,4 @@ def test_PlannersInterfacee__init__sets_instance_ai_constructs_to_a_list_of_the_ assert cut.ai_constructs == expected_ai_constructs -# update tests -def test_PlannersInterface_update_only_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_when_instance_ai_constructs_is_empty(mocker): - # Arrange - arg_curr_data = MagicMock() - arg_status = MagicMock() - - mocker.patch(planners_interface.__name__ + '.status_to_oneHot') - - cut = PlannersInterface.__new__(PlannersInterface) - cut.ai_constructs = [] - - # Act - result = cut.update(arg_curr_data, arg_status) - - # Assert - assert planners_interface.status_to_oneHot.call_count == 1 - assert planners_interface.status_to_oneHot.call_args_list[0].args == (arg_status,) - assert result == None - -def test_PlannersInterface_update_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_and_calls_update_on_each_ai_construct_with_input_data_when_instance_ai_constructs_is_occupied(mocker): - # Arrange - arg_curr_data = MagicMock() - arg_status = MagicMock() - - mocker.patch(planners_interface.__name__ + '.status_to_oneHot') - - cut = PlannersInterface.__new__(PlannersInterface) - cut.ai_constructs = [] - - num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) - for i in range(num_fake_ai_constructs): - fake_ai_construct = MagicMock() - cut.ai_constructs.append(fake_ai_construct) - mocker.patch.object(fake_ai_construct, 'update') - - # Act - result = cut.update(arg_curr_data, arg_status) - - # Assert - assert planners_interface.status_to_oneHot.call_count == 1 - assert planners_interface.status_to_oneHot.call_args_list[0].args == (arg_status,) - for i in range(num_fake_ai_constructs): - assert cut.ai_constructs[i].update.call_count == 1 - assert cut.ai_constructs[i].update.call_args_list[0].args == (arg_curr_data,) - assert result == None - -# apriori_training tests -def test_PlannersInterface_apriori_training_does_nothing_when_instance_ai_constructs_is_empty(): - # Arrange - arg_batch_data = MagicMock() - - cut = PlannersInterface.__new__(PlannersInterface) - cut.ai_constructs = [] - - # Act - result = cut.apriori_training(arg_batch_data) - - # Assert - assert result == None -def test_PlannersInterface_apriori_training_calls_apriori_training_on_each_ai_constructs_item(mocker): - # Arrange - arg_batch_data = MagicMock() - - cut = PlannersInterface.__new__(PlannersInterface) - cut.ai_constructs = [] - - num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) - for i in range(num_fake_ai_constructs): - cut.ai_constructs.append(MagicMock()) - - # Act - result = cut.apriori_training(arg_batch_data) - - # Assert - for i in range(num_fake_ai_constructs): - assert cut.ai_constructs[i].apriori_training.call_count == 1 - assert cut.ai_constructs[i].apriori_training.call_args_list[0].args == (arg_batch_data, ) - assert result == None - -# render_reasoning tests -def test_PlannersInterface_render_reasoning_returns_empty_dict_when_instance_ai_constructs_is_empty(mocker): - # Arrange - cut = PlannersInterface.__new__(PlannersInterface) - cut.ai_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_ai_constructs_is_occupied(mocker): - # Arrange - cut = PlannersInterface.__new__(PlannersInterface) - cut.ai_constructs = [] - - expected_result = {} - - num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) - for i in range(num_fake_ai_constructs): - fake_ai_construct = MagicMock() - forced_return_ai_construct_render_reasoning = MagicMock() - cut.ai_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_ai_constructs): - assert cut.ai_constructs[i].render_reasoning.call_count == 1 - assert cut.ai_constructs[i].render_reasoning.call_args_list[0].args == () - assert result == expected_result \ No newline at end of file From 42392646cba2851eb566eface0ccdd0bed0a8107 Mon Sep 17 00:00:00 2001 From: Evana Gizzi Date: Thu, 5 Oct 2023 14:58:11 -0400 Subject: [PATCH 06/18] added a planning intrface and renamed the DDL interface to learners. Now we have planners and learners. Closes #50 --- .../__init__.py | 0 .../ai_plugin_abstract/__init__.py | 0 .../ai_plugin_abstract/core.py | 0 .../learners_interface.py} | 2 +- onair/src/ai_components/planners_interface.py | 46 ++++++++++++++++ onair/src/reasoning/agent.py | 4 +- plugins/generic/generic_plugin.py | 2 +- plugins/kalman_plugin/kalman_plugin.py | 2 +- .../ai_plugin_abstract/test_core.py | 4 +- .../test_learners_interface.py} | 52 +++++++++---------- test/onair/src/reasoning/test_agent.py | 6 +-- 11 files changed, 82 insertions(+), 36 deletions(-) rename onair/src/{data_driven_components => ai_components}/__init__.py (100%) rename onair/src/{data_driven_components => ai_components}/ai_plugin_abstract/__init__.py (100%) rename onair/src/{data_driven_components => ai_components}/ai_plugin_abstract/core.py (100%) rename onair/src/{data_driven_components/data_driven_learning.py => ai_components/learners_interface.py} (98%) create mode 100644 onair/src/ai_components/planners_interface.py rename test/onair/src/{data_driven_components => ai_components}/ai_plugin_abstract/test_core.py (96%) rename test/onair/src/{data_driven_components/test_data_driven_learning.py => ai_components/test_learners_interface.py} (70%) diff --git a/onair/src/data_driven_components/__init__.py b/onair/src/ai_components/__init__.py similarity index 100% rename from onair/src/data_driven_components/__init__.py rename to onair/src/ai_components/__init__.py diff --git a/onair/src/data_driven_components/ai_plugin_abstract/__init__.py b/onair/src/ai_components/ai_plugin_abstract/__init__.py similarity index 100% rename from onair/src/data_driven_components/ai_plugin_abstract/__init__.py rename to onair/src/ai_components/ai_plugin_abstract/__init__.py diff --git a/onair/src/data_driven_components/ai_plugin_abstract/core.py b/onair/src/ai_components/ai_plugin_abstract/core.py similarity index 100% rename from onair/src/data_driven_components/ai_plugin_abstract/core.py rename to onair/src/ai_components/ai_plugin_abstract/core.py diff --git a/onair/src/data_driven_components/data_driven_learning.py b/onair/src/ai_components/learners_interface.py similarity index 98% rename from onair/src/data_driven_components/data_driven_learning.py rename to onair/src/ai_components/learners_interface.py index 4a37d491..d6677b33 100644 --- a/onair/src/data_driven_components/data_driven_learning.py +++ b/onair/src/ai_components/learners_interface.py @@ -15,7 +15,7 @@ from ..util.data_conversion import * -class DataDrivenLearning: +class LearnersInterface: def __init__(self, headers, _ai_plugins={}): assert(len(headers)>0), 'Headers are required' self.headers = headers diff --git a/onair/src/ai_components/planners_interface.py b/onair/src/ai_components/planners_interface.py new file mode 100644 index 00000000..80019ee4 --- /dev/null +++ b/onair/src/ai_components/planners_interface.py @@ -0,0 +1,46 @@ +# GSC-19165-1, "The On-Board Artificial Intelligence Research (OnAIR) Platform" +# +# Copyright © 2023 United States Government as represented by the Administrator of +# the National Aeronautics and Space Administration. No copyright is claimed in the +# United States under Title 17, U.S. Code. All Other Rights Reserved. +# +# Licensed under the NASA Open Source Agreement version 1.3 +# See "NOSA GSC-19165-1 OnAIR.pdf" + +""" +Planners interface class for managing all planning-based AI components +""" +import importlib.util +import importlib.util + +from ..util.data_conversion import * + +class PlannersInterface: + def __init__(self, headers, _ai_plugins={}): + assert(len(headers)>0), 'Headers are required' + self.headers = headers + self.planning_constructs = [] + for module_name in list(_planning_plugins.keys()): + spec = importlib.util.spec_from_file_location(module_name, _planning_plugins[module_name]) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + self.planning_constructs.append(module.Plugin(module_name,headers)) + + def update(self, curr_data, status): + input_data = curr_data + output_data = status_to_oneHot(status) + for plugin in self.planning_constructs: + plugin.update(input_data) + + def apriori_training(self, batch_data): + for plugin in self.planning_constructs: + plugin.apriori_training(batch_data) + + def render_reasoning(self): + diagnoses = {} + for plugin in self.planning_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 6a28a16d..83dcde33 100644 --- a/onair/src/reasoning/agent.py +++ b/onair/src/reasoning/agent.py @@ -11,13 +11,13 @@ Agent Class Deals with supervised learning for diagnosing statuses """ -from ..data_driven_components.data_driven_learning import DataDrivenLearning +from ..ai_components.learners_interface import LearnersInterface from ..reasoning.diagnosis import Diagnosis class Agent: def __init__(self, vehicle, plugin_list): self.vehicle_rep = vehicle - self.learning_systems = DataDrivenLearning(self.vehicle_rep.get_headers(),plugin_list) + self.learning_systems = LearnersInterface(self.vehicle_rep.get_headers(),plugin_list) self.mission_status = self.vehicle_rep.get_status() self.bayesian_status = self.vehicle_rep.get_bayesian_status() diff --git a/plugins/generic/generic_plugin.py b/plugins/generic/generic_plugin.py index 1b0da3ae..6a8a9acd 100644 --- a/plugins/generic/generic_plugin.py +++ b/plugins/generic/generic_plugin.py @@ -8,7 +8,7 @@ # See "NOSA GSC-19165-1 OnAIR.pdf" import numpy as np -from onair.src.data_driven_components.ai_plugin_abstract.core import AIPlugIn +from onair.src.ai_components.ai_plugin_abstract.core import AIPlugIn class Plugin(AIPlugIn): def apriori_training(self,batch_data=[]): diff --git a/plugins/kalman_plugin/kalman_plugin.py b/plugins/kalman_plugin/kalman_plugin.py index 7a557361..f97bb62a 100644 --- a/plugins/kalman_plugin/kalman_plugin.py +++ b/plugins/kalman_plugin/kalman_plugin.py @@ -9,7 +9,7 @@ import simdkalman import numpy as np -from onair.src.data_driven_components.ai_plugin_abstract.core import AIPlugIn +from onair.src.ai_components.ai_plugin_abstract.core import AIPlugIn class Plugin(AIPlugIn): def __init__(self, name, headers, window_size=3): diff --git a/test/onair/src/data_driven_components/ai_plugin_abstract/test_core.py b/test/onair/src/ai_components/ai_plugin_abstract/test_core.py similarity index 96% rename from test/onair/src/data_driven_components/ai_plugin_abstract/test_core.py rename to test/onair/src/ai_components/ai_plugin_abstract/test_core.py index 7667b5fb..ec7eb6ea 100644 --- a/test/onair/src/data_driven_components/ai_plugin_abstract/test_core.py +++ b/test/onair/src/ai_components/ai_plugin_abstract/test_core.py @@ -11,8 +11,8 @@ import pytest from mock import MagicMock -import onair.src.data_driven_components.ai_plugin_abstract.core as core -from onair.src.data_driven_components.ai_plugin_abstract.core import AIPlugIn +import onair.src.ai_components.ai_plugin_abstract.core as core +from onair.src.ai_components.ai_plugin_abstract.core import AIPlugIn class FakeAIPlugIn(AIPlugIn): def __init__(self, _name, _headers): diff --git a/test/onair/src/data_driven_components/test_data_driven_learning.py b/test/onair/src/ai_components/test_learners_interface.py similarity index 70% rename from test/onair/src/data_driven_components/test_data_driven_learning.py rename to test/onair/src/ai_components/test_learners_interface.py index e39d89e1..9bd737e4 100644 --- a/test/onair/src/data_driven_components/test_data_driven_learning.py +++ b/test/onair/src/ai_components/test_learners_interface.py @@ -7,18 +7,18 @@ # Licensed under the NASA Open Source Agreement version 1.3 # See "NOSA GSC-19165-1 OnAIR.pdf" -""" Test DataDrivenLearning Functionality """ +""" Test LearnersInterface Functionality """ import pytest from mock import MagicMock -import onair.src.data_driven_components.data_driven_learning as data_driven_learning -from onair.src.data_driven_components.data_driven_learning import DataDrivenLearning +import onair.src.ai_components.learners_interface as learners_interface +from onair.src.ai_components.learners_interface import LearnersInterface import importlib from typing import Dict # __init__ tests -def test_DataDrivenLearning__init__sets_instance_headers_to_given_headers_and_does_nothing_else_when_given__ai_plugins_is_empty(mocker): +def test_LearnersInterface__init__sets_instance_headers_to_given_headers_and_does_nothing_else_when_given__ai_plugins_is_empty(mocker): # Arrange arg_headers = [] arg__ai_plugins = {} @@ -27,7 +27,7 @@ def test_DataDrivenLearning__init__sets_instance_headers_to_given_headers_and_do for i in range(num_fake_headers): arg_headers.append(MagicMock()) - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) # Act cut.__init__(arg_headers, arg__ai_plugins) @@ -35,7 +35,7 @@ def test_DataDrivenLearning__init__sets_instance_headers_to_given_headers_and_do # Assert assert cut.headers == arg_headers -def test_DataDrivenLearning__init__throws_AttributeError_when_given_module_file_has_no_attribute_Plugin(mocker): +def test_LearnersInterface__init__throws_AttributeError_when_given_module_file_has_no_attribute_Plugin(mocker): # Arrange fake_module_name = MagicMock() arg_headers = [] @@ -48,7 +48,7 @@ def test_DataDrivenLearning__init__throws_AttributeError_when_given_module_file_ # Assert -def test_DataDrivenLearning__init__sets_instance_ai_constructs_to_a_list_of_the_calls_AIPlugIn_with_plugin_and_given_headers_for_each_item_in_given__ai_plugins_when_given__ai_plugins_is_occupied(mocker): +def test_LearnersInterface__init__sets_instance_ai_constructs_to_a_list_of_the_calls_AIPlugIn_with_plugin_and_given_headers_for_each_item_in_given__ai_plugins_when_given__ai_plugins_is_occupied(mocker): # Arrange arg_headers = [] arg__ai_plugins = {} @@ -77,7 +77,7 @@ def test_DataDrivenLearning__init__sets_instance_ai_constructs_to_a_list_of_the_ for i, module in enumerate(fake_module_list): mocker.patch.object(module,'Plugin',return_value=expected_ai_constructs[i]) - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) # Act cut.__init__(arg_headers, arg__ai_plugins) @@ -99,32 +99,32 @@ def test_DataDrivenLearning__init__sets_instance_ai_constructs_to_a_list_of_the_ assert cut.ai_constructs == expected_ai_constructs # update tests -def test_DataDrivenLearning_update_only_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_when_instance_ai_constructs_is_empty(mocker): +def test_LearnersInterface_update_only_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_when_instance_ai_constructs_is_empty(mocker): # Arrange arg_curr_data = MagicMock() arg_status = MagicMock() - mocker.patch(data_driven_learning.__name__ + '.status_to_oneHot') + mocker.patch(learners_interface.__name__ + '.status_to_oneHot') - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) cut.ai_constructs = [] # Act result = cut.update(arg_curr_data, arg_status) # Assert - assert data_driven_learning.status_to_oneHot.call_count == 1 - assert data_driven_learning.status_to_oneHot.call_args_list[0].args == (arg_status,) + assert learners_interface.status_to_oneHot.call_count == 1 + assert learners_interface.status_to_oneHot.call_args_list[0].args == (arg_status,) assert result == None -def test_DataDrivenLearning_update_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_and_calls_update_on_each_ai_construct_with_input_data_when_instance_ai_constructs_is_occupied(mocker): +def test_LearnersInterface_update_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_and_calls_update_on_each_ai_construct_with_input_data_when_instance_ai_constructs_is_occupied(mocker): # Arrange arg_curr_data = MagicMock() arg_status = MagicMock() - mocker.patch(data_driven_learning.__name__ + '.status_to_oneHot') + mocker.patch(learners_interface.__name__ + '.status_to_oneHot') - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) cut.ai_constructs = [] num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) @@ -137,19 +137,19 @@ def test_DataDrivenLearning_update_calls_flotify_input_with_given_curr_data_and_ result = cut.update(arg_curr_data, arg_status) # Assert - assert data_driven_learning.status_to_oneHot.call_count == 1 - assert data_driven_learning.status_to_oneHot.call_args_list[0].args == (arg_status,) + assert learners_interface.status_to_oneHot.call_count == 1 + assert learners_interface.status_to_oneHot.call_args_list[0].args == (arg_status,) for i in range(num_fake_ai_constructs): assert cut.ai_constructs[i].update.call_count == 1 assert cut.ai_constructs[i].update.call_args_list[0].args == (arg_curr_data,) assert result == None # apriori_training tests -def test_DataDrivenLearning_apriori_training_does_nothing_when_instance_ai_constructs_is_empty(): +def test_LearnersInterface_apriori_training_does_nothing_when_instance_ai_constructs_is_empty(): # Arrange arg_batch_data = MagicMock() - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) cut.ai_constructs = [] # Act @@ -158,11 +158,11 @@ def test_DataDrivenLearning_apriori_training_does_nothing_when_instance_ai_const # Assert assert result == None -def test_DataDrivenLearning_apriori_training_calls_apriori_training_on_each_ai_constructs_item(mocker): +def test_LearnersInterface_apriori_training_calls_apriori_training_on_each_ai_constructs_item(mocker): # Arrange arg_batch_data = MagicMock() - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) cut.ai_constructs = [] num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) @@ -179,9 +179,9 @@ def test_DataDrivenLearning_apriori_training_calls_apriori_training_on_each_ai_c assert result == None # render_reasoning tests -def test_DataDrivenLearning_render_reasoning_returns_empty_dict_when_instance_ai_constructs_is_empty(mocker): +def test_LearnersInterface_render_reasoning_returns_empty_dict_when_instance_ai_constructs_is_empty(mocker): # Arrange - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) cut.ai_constructs = [] # Act @@ -190,9 +190,9 @@ def test_DataDrivenLearning_render_reasoning_returns_empty_dict_when_instance_ai # Assert assert result == {} -def test_DataDrivenLearning_render_reasoning_returns_dict_of_each_ai_construct_as_key_to_the_result_of_its_render_reasoning_when_instance_ai_constructs_is_occupied(mocker): +def test_LearnersInterface_render_reasoning_returns_dict_of_each_ai_construct_as_key_to_the_result_of_its_render_reasoning_when_instance_ai_constructs_is_occupied(mocker): # Arrange - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) cut.ai_constructs = [] expected_result = {} diff --git a/test/onair/src/reasoning/test_agent.py b/test/onair/src/reasoning/test_agent.py index 5713ea06..cef204ce 100644 --- a/test/onair/src/reasoning/test_agent.py +++ b/test/onair/src/reasoning/test_agent.py @@ -26,7 +26,7 @@ def test_Agent__init__sets_vehicle_rep_to_given_vehicle_and_learning_systems_and fake_plugin_list = MagicMock() mocker.patch.object(arg_vehicle, 'get_headers', return_value=fake_headers) - mocker.patch(agent.__name__ + '.DataDrivenLearning', return_value=fake_learning_systems) + mocker.patch(agent.__name__ + '.LearnersInterface', return_value=fake_learning_systems) mocker.patch.object(arg_vehicle, 'get_status', return_value=fake_mission_status) mocker.patch.object(arg_vehicle, 'get_bayesian_status', return_value=fake_bayesian_status) @@ -39,8 +39,8 @@ def test_Agent__init__sets_vehicle_rep_to_given_vehicle_and_learning_systems_and assert cut.vehicle_rep == arg_vehicle assert arg_vehicle.get_headers.call_count == 1 assert arg_vehicle.get_headers.call_args_list[0].args == () - assert agent.DataDrivenLearning.call_count == 1 - assert agent.DataDrivenLearning.call_args_list[0].args == (fake_headers, fake_plugin_list) + assert agent.LearnersInterface.call_count == 1 + assert agent.LearnersInterface.call_args_list[0].args == (fake_headers, fake_plugin_list) assert cut.learning_systems == fake_learning_systems assert arg_vehicle.get_status.call_count == 1 assert arg_vehicle.get_status.call_args_list[0].args == () From 3e44cff480bc70b92f78f5ead51681c3391c4622 Mon Sep 17 00:00:00 2001 From: Evana Gizzi Date: Thu, 5 Oct 2023 21:29:44 -0400 Subject: [PATCH 07/18] Needed to add a test_planner_interface suite --- onair/src/ai_components/planners_interface.py | 14 +- onair/src/reasoning/agent.py | 7 +- .../ai_components/test_planners_interface.py | 214 ++++++++++++++++++ test/onair/src/reasoning/test_agent.py | 11 +- 4 files changed, 237 insertions(+), 9 deletions(-) create mode 100644 test/onair/src/ai_components/test_planners_interface.py diff --git a/onair/src/ai_components/planners_interface.py b/onair/src/ai_components/planners_interface.py index 80019ee4..78a1666e 100644 --- a/onair/src/ai_components/planners_interface.py +++ b/onair/src/ai_components/planners_interface.py @@ -19,26 +19,26 @@ class PlannersInterface: def __init__(self, headers, _ai_plugins={}): assert(len(headers)>0), 'Headers are required' self.headers = headers - self.planning_constructs = [] - for module_name in list(_planning_plugins.keys()): - spec = importlib.util.spec_from_file_location(module_name, _planning_plugins[module_name]) + self.ai_constructs = [] + for module_name in list(_ai_plugins.keys()): + spec = importlib.util.spec_from_file_location(module_name, _ai_plugins[module_name]) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) - self.planning_constructs.append(module.Plugin(module_name,headers)) + self.ai_constructs.append(module.Plugin(module_name,headers)) def update(self, curr_data, status): input_data = curr_data output_data = status_to_oneHot(status) - for plugin in self.planning_constructs: + for plugin in self.ai_constructs: plugin.update(input_data) def apriori_training(self, batch_data): - for plugin in self.planning_constructs: + for plugin in self.ai_constructs: plugin.apriori_training(batch_data) def render_reasoning(self): diagnoses = {} - for plugin in self.planning_constructs: + for plugin in self.ai_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 83dcde33..2b827ced 100644 --- a/onair/src/reasoning/agent.py +++ b/onair/src/reasoning/agent.py @@ -12,20 +12,25 @@ Deals with supervised learning for diagnosing statuses """ from ..ai_components.learners_interface import LearnersInterface +from ..ai_components.planners_interface import PlannersInterface from ..reasoning.diagnosis import Diagnosis class Agent: def __init__(self, vehicle, plugin_list): self.vehicle_rep = vehicle - self.learning_systems = LearnersInterface(self.vehicle_rep.get_headers(),plugin_list) self.mission_status = self.vehicle_rep.get_status() self.bayesian_status = self.vehicle_rep.get_bayesian_status() + # AI Interfaces + self.learning_systems = LearnersInterface(self.vehicle_rep.get_headers(),plugin_list) + self.planning_systems = PlannersInterface(self.vehicle_rep.get_headers(),plugin_list) + # Markov Assumption holds def reason(self, frame): self.vehicle_rep.update(frame) self.mission_status = self.vehicle_rep.get_status() self.learning_systems.update(frame, self.mission_status) + self.planning_systems.update(frame, self.mission_status) def diagnose(self, time_step): """ Grab the mnemonics from the """ diff --git a/test/onair/src/ai_components/test_planners_interface.py b/test/onair/src/ai_components/test_planners_interface.py new file mode 100644 index 00000000..bae6fc7d --- /dev/null +++ b/test/onair/src/ai_components/test_planners_interface.py @@ -0,0 +1,214 @@ +# GSC-19165-1, "The On-Board Artificial Intelligence Research (OnAIR) Platform" +# +# Copyright © 2023 United States Government as represented by the Administrator of +# the National Aeronautics and Space Administration. No copyright is claimed in the +# United States under Title 17, U.S. Code. All Other Rights Reserved. +# +# Licensed under the NASA Open Source Agreement version 1.3 +# See "NOSA GSC-19165-1 OnAIR.pdf" + +""" Test PlannersInterface Functionality """ +import pytest +from mock import MagicMock + +import onair.src.ai_components.planners_interface as planners_interface +from onair.src.ai_components.planners_interface import PlannersInterface + +import importlib +from typing import Dict + +# __init__ tests +def test_PlannersInterface__init__sets_instance_headers_to_given_headers_and_does_nothing_else_when_given__ai_plugins_is_empty(mocker): + # Arrange + arg_headers = [] + arg__ai_plugins = {} + + num_fake_headers = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 headers (0 has own test) + for i in range(num_fake_headers): + arg_headers.append(MagicMock()) + + cut = PlannersInterface.__new__(PlannersInterface) + + # Act + cut.__init__(arg_headers, arg__ai_plugins) + + # Assert + assert cut.headers == arg_headers + +def test_PlannersInterface__init__throws_AttributeError_when_given_module_file_has_no_attribute_Plugin(mocker): + # Arrange + fake_module_name = MagicMock() + arg_headers = [] + + arg__ai_plugins = {MagicMock()} + + # Act + + # Assert + +def test_PlannersInterfacee__init__sets_instance_ai_constructs_to_a_list_of_the_calls_AIPlugIn_with_plugin_and_given_headers_for_each_item_in_given__ai_plugins_when_given__ai_plugins_is_occupied(mocker): + # Arrange + arg_headers = [] + arg__ai_plugins = {} + fake_spec_list = [] + fake_module_list = [] + + num_fake_headers = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 headers (0 has own test) + for i in range(num_fake_headers): + arg_headers.append(MagicMock()) + num_fake_ai_plugins = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) + for i in range(num_fake_ai_plugins): + arg__ai_plugins[str(MagicMock())] = str(MagicMock()) + fake_spec_list.append(MagicMock()) + fake_module_list.append(MagicMock()) + + + expected_ai_constructs = [] + for i in range(num_fake_ai_plugins): + expected_ai_constructs.append(MagicMock()) + + # mocker.patch('importlib.import_module', return_value=fake_imported_module) + mocker.patch('importlib.util.spec_from_file_location',side_effect=fake_spec_list) + mocker.patch('importlib.util.module_from_spec',side_effect=fake_module_list) + for spec in fake_spec_list: + mocker.patch.object(spec,'loader.exec_module') + for i, module in enumerate(fake_module_list): + mocker.patch.object(module,'Plugin',return_value=expected_ai_constructs[i]) + + cut = PlannersInterface.__new__(PlannersInterface) + + # Act + cut.__init__(arg_headers, arg__ai_plugins) + + # Assert + assert importlib.util.spec_from_file_location.call_count == len(arg__ai_plugins) + assert importlib.util.module_from_spec.call_count == len(fake_spec_list) + + for i in range(num_fake_ai_plugins): + fake_name = list(arg__ai_plugins.keys())[i] + fake_path = arg__ai_plugins[fake_name] + assert importlib.util.spec_from_file_location.call_args_list[i].args == (fake_name,fake_path) + assert importlib.util.module_from_spec.call_args_list[i].args == (fake_spec_list[i],) + assert fake_spec_list[i].loader.exec_module.call_count == 1 + assert fake_spec_list[i].loader.exec_module.call_args_list[0].args == (fake_module_list[i],) + assert fake_module_list[i].Plugin.call_count == 1 + assert fake_module_list[i].Plugin.call_args_list[0].args == (fake_name,arg_headers) + + assert cut.ai_constructs == expected_ai_constructs + +# update tests +def test_PlannersInterface_update_only_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_when_instance_ai_constructs_is_empty(mocker): + # Arrange + arg_curr_data = MagicMock() + arg_status = MagicMock() + + mocker.patch(planners_interface.__name__ + '.status_to_oneHot') + + cut = PlannersInterface.__new__(PlannersInterface) + cut.ai_constructs = [] + + # Act + result = cut.update(arg_curr_data, arg_status) + + # Assert + assert planners_interface.status_to_oneHot.call_count == 1 + assert planners_interface.status_to_oneHot.call_args_list[0].args == (arg_status,) + assert result == None + +def test_PlannersInterface_update_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_and_calls_update_on_each_ai_construct_with_input_data_when_instance_ai_constructs_is_occupied(mocker): + # Arrange + arg_curr_data = MagicMock() + arg_status = MagicMock() + + mocker.patch(planners_interface.__name__ + '.status_to_oneHot') + + cut = PlannersInterface.__new__(PlannersInterface) + cut.ai_constructs = [] + + num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) + for i in range(num_fake_ai_constructs): + fake_ai_construct = MagicMock() + cut.ai_constructs.append(fake_ai_construct) + mocker.patch.object(fake_ai_construct, 'update') + + # Act + result = cut.update(arg_curr_data, arg_status) + + # Assert + assert planners_interface.status_to_oneHot.call_count == 1 + assert planners_interface.status_to_oneHot.call_args_list[0].args == (arg_status,) + for i in range(num_fake_ai_constructs): + assert cut.ai_constructs[i].update.call_count == 1 + assert cut.ai_constructs[i].update.call_args_list[0].args == (arg_curr_data,) + assert result == None + +# apriori_training tests +def test_PlannersInterface_apriori_training_does_nothing_when_instance_ai_constructs_is_empty(): + # Arrange + arg_batch_data = MagicMock() + + cut = PlannersInterface.__new__(PlannersInterface) + cut.ai_constructs = [] + + # Act + result = cut.apriori_training(arg_batch_data) + + # Assert + assert result == None + +def test_PlannersInterface_apriori_training_calls_apriori_training_on_each_ai_constructs_item(mocker): + # Arrange + arg_batch_data = MagicMock() + + cut = PlannersInterface.__new__(PlannersInterface) + cut.ai_constructs = [] + + num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) + for i in range(num_fake_ai_constructs): + cut.ai_constructs.append(MagicMock()) + + # Act + result = cut.apriori_training(arg_batch_data) + + # Assert + for i in range(num_fake_ai_constructs): + assert cut.ai_constructs[i].apriori_training.call_count == 1 + assert cut.ai_constructs[i].apriori_training.call_args_list[0].args == (arg_batch_data, ) + assert result == None + +# render_reasoning tests +def test_PlannersInterface_render_reasoning_returns_empty_dict_when_instance_ai_constructs_is_empty(mocker): + # Arrange + cut = PlannersInterface.__new__(PlannersInterface) + cut.ai_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_ai_constructs_is_occupied(mocker): + # Arrange + cut = PlannersInterface.__new__(PlannersInterface) + cut.ai_constructs = [] + + expected_result = {} + + num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) + for i in range(num_fake_ai_constructs): + fake_ai_construct = MagicMock() + forced_return_ai_construct_render_reasoning = MagicMock() + cut.ai_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_ai_constructs): + assert cut.ai_constructs[i].render_reasoning.call_count == 1 + assert cut.ai_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 cef204ce..65c37aa2 100644 --- a/test/onair/src/reasoning/test_agent.py +++ b/test/onair/src/reasoning/test_agent.py @@ -21,12 +21,14 @@ def test_Agent__init__sets_vehicle_rep_to_given_vehicle_and_learning_systems_and fake_headers = MagicMock() fake_learning_systems = MagicMock() + fake_planning_systems = MagicMock() fake_mission_status = MagicMock() fake_bayesian_status = MagicMock() fake_plugin_list = MagicMock() mocker.patch.object(arg_vehicle, 'get_headers', return_value=fake_headers) mocker.patch(agent.__name__ + '.LearnersInterface', return_value=fake_learning_systems) + mocker.patch(agent.__name__ + '.PlannersInterface', return_value=fake_planning_systems) mocker.patch.object(arg_vehicle, 'get_status', return_value=fake_mission_status) mocker.patch.object(arg_vehicle, 'get_bayesian_status', return_value=fake_bayesian_status) @@ -37,11 +39,14 @@ def test_Agent__init__sets_vehicle_rep_to_given_vehicle_and_learning_systems_and # Assert assert cut.vehicle_rep == arg_vehicle - assert arg_vehicle.get_headers.call_count == 1 + assert arg_vehicle.get_headers.call_count == 2 assert arg_vehicle.get_headers.call_args_list[0].args == () assert agent.LearnersInterface.call_count == 1 assert agent.LearnersInterface.call_args_list[0].args == (fake_headers, fake_plugin_list) assert cut.learning_systems == fake_learning_systems + assert agent.PlannersInterface.call_count == 1 + assert agent.PlannersInterface.call_args_list[0].args == (fake_headers, fake_plugin_list) + assert cut.planning_systems == fake_planning_systems assert arg_vehicle.get_status.call_count == 1 assert arg_vehicle.get_status.call_args_list[0].args == () assert cut.mission_status == fake_mission_status @@ -59,6 +64,7 @@ def test_Agent_reason_updates_vehicle_rep_with_given_frame_and_sets_new_vehicle_ cut = Agent.__new__(Agent) cut.vehicle_rep = MagicMock() cut.learning_systems = MagicMock() + cut.planning_systems = MagicMock() mocker.patch.object(cut.vehicle_rep, 'update') mocker.patch.object(cut.vehicle_rep, 'get_status', return_value=fake_mission_status) @@ -74,6 +80,8 @@ def test_Agent_reason_updates_vehicle_rep_with_given_frame_and_sets_new_vehicle_ assert cut.vehicle_rep.get_status.call_args_list[0].args == () assert cut.learning_systems.update.call_count == 1 assert cut.learning_systems.update.call_args_list[0].args == (arg_frame, fake_mission_status) + assert cut.planning_systems.update.call_count == 1 + assert cut.planning_systems.update.call_args_list[0].args == (arg_frame, fake_mission_status) # diagnose tests def test_Agent_diagnose_returns_empty_Dict(): @@ -82,6 +90,7 @@ def test_Agent_diagnose_returns_empty_Dict(): cut = Agent.__new__(Agent) cut.learning_systems = MagicMock() + cut.planning_systems = MagicMock() cut.bayesian_status = MagicMock() cut.vehicle_rep = MagicMock() From 554e34ee709c5186895ec09264d1f76a2f2c3a24 Mon Sep 17 00:00:00 2001 From: Evana Gizzi Date: Thu, 5 Oct 2023 21:31:58 -0400 Subject: [PATCH 08/18] removed extra lines and double import --- onair/src/ai_components/learners_interface.py | 4 ---- onair/src/ai_components/planners_interface.py | 4 ---- 2 files changed, 8 deletions(-) diff --git a/onair/src/ai_components/learners_interface.py b/onair/src/ai_components/learners_interface.py index d6677b33..76f4058b 100644 --- a/onair/src/ai_components/learners_interface.py +++ b/onair/src/ai_components/learners_interface.py @@ -11,7 +11,6 @@ Data driven learning class for managing all data driven AI components """ import importlib.util -import importlib.util from ..util.data_conversion import * @@ -41,6 +40,3 @@ def render_reasoning(self): for plugin in self.ai_constructs: diagnoses[plugin.component_name] = plugin.render_reasoning() return diagnoses - - - diff --git a/onair/src/ai_components/planners_interface.py b/onair/src/ai_components/planners_interface.py index 78a1666e..7de0483c 100644 --- a/onair/src/ai_components/planners_interface.py +++ b/onair/src/ai_components/planners_interface.py @@ -11,7 +11,6 @@ Planners interface class for managing all planning-based AI components """ import importlib.util -import importlib.util from ..util.data_conversion import * @@ -41,6 +40,3 @@ def render_reasoning(self): for plugin in self.ai_constructs: diagnoses[plugin.component_name] = plugin.render_reasoning() return diagnoses - - - From 036127de1feaf8c6ac9081e22006aae8a4c49ae6 Mon Sep 17 00:00:00 2001 From: Evana Gizzi Date: Thu, 5 Oct 2023 21:43:40 -0400 Subject: [PATCH 09/18] Changed the planner interface a bit in order to handle the disambiguation of planners versus learners --- onair/src/ai_components/learners_interface.py | 9 ++++++--- onair/src/ai_components/planners_interface.py | 19 +++++++++---------- onair/src/reasoning/agent.py | 5 +++++ 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/onair/src/ai_components/learners_interface.py b/onair/src/ai_components/learners_interface.py index 76f4058b..b07c2327 100644 --- a/onair/src/ai_components/learners_interface.py +++ b/onair/src/ai_components/learners_interface.py @@ -25,15 +25,18 @@ def __init__(self, headers, _ai_plugins={}): spec.loader.exec_module(module) self.ai_constructs.append(module.Plugin(module_name,headers)) + def apriori_training(self, batch_data): + for plugin in self.ai_constructs: + plugin.apriori_training(batch_data) + def update(self, curr_data, status): input_data = curr_data output_data = status_to_oneHot(status) for plugin in self.ai_constructs: plugin.update(input_data) - def apriori_training(self, batch_data): - for plugin in self.ai_constructs: - plugin.apriori_training(batch_data) + def check_for_salient_event(self): + pass def render_reasoning(self): diagnoses = {} diff --git a/onair/src/ai_components/planners_interface.py b/onair/src/ai_components/planners_interface.py index 7de0483c..bc9a366d 100644 --- a/onair/src/ai_components/planners_interface.py +++ b/onair/src/ai_components/planners_interface.py @@ -25,18 +25,17 @@ def __init__(self, headers, _ai_plugins={}): spec.loader.exec_module(module) self.ai_constructs.append(module.Plugin(module_name,headers)) - def update(self, curr_data, status): - input_data = curr_data - output_data = status_to_oneHot(status) - for plugin in self.ai_constructs: - plugin.update(input_data) - def apriori_training(self, batch_data): for plugin in self.ai_constructs: plugin.apriori_training(batch_data) + def update(self, curr_raw_tlm): + # Raw TLM should be transformed into high-leve state representation here + # Can store something as stale unless a planning thread is launched + pass + + def check_for_salient_event(self): + pass + def render_reasoning(self): - diagnoses = {} - for plugin in self.ai_constructs: - diagnoses[plugin.component_name] = plugin.render_reasoning() - return diagnoses + pass \ No newline at end of file diff --git a/onair/src/reasoning/agent.py b/onair/src/reasoning/agent.py index 2b827ced..9b83a5ee 100644 --- a/onair/src/reasoning/agent.py +++ b/onair/src/reasoning/agent.py @@ -27,11 +27,16 @@ def __init__(self, vehicle, plugin_list): # Markov Assumption holds def reason(self, frame): + # Update with new telemetry self.vehicle_rep.update(frame) self.mission_status = self.vehicle_rep.get_status() self.learning_systems.update(frame, self.mission_status) self.planning_systems.update(frame, self.mission_status) + # Check for a salient event, needing acionable outcome + self.learning_systems.check_for_salient_event() + self.planning_systems.check_for_salient_event() + def diagnose(self, time_step): """ Grab the mnemonics from the """ learning_system_results = self.learning_systems.render_reasoning() From 8b38584e4058a09aac10336b2782423942b5ee97 Mon Sep 17 00:00:00 2001 From: Evana Gizzi Date: Thu, 5 Oct 2023 21:55:11 -0400 Subject: [PATCH 10/18] updated tests --- onair/src/ai_components/planners_interface.py | 8 +- .../ai_components/test_planners_interface.py | 115 ------------------ 2 files changed, 2 insertions(+), 121 deletions(-) diff --git a/onair/src/ai_components/planners_interface.py b/onair/src/ai_components/planners_interface.py index bc9a366d..f8339b86 100644 --- a/onair/src/ai_components/planners_interface.py +++ b/onair/src/ai_components/planners_interface.py @@ -24,12 +24,8 @@ def __init__(self, headers, _ai_plugins={}): module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) self.ai_constructs.append(module.Plugin(module_name,headers)) - - def apriori_training(self, batch_data): - for plugin in self.ai_constructs: - plugin.apriori_training(batch_data) - def update(self, curr_raw_tlm): + def update(self, curr_raw_tlm, status): # Raw TLM should be transformed into high-leve state representation here # Can store something as stale unless a planning thread is launched pass @@ -38,4 +34,4 @@ def check_for_salient_event(self): pass def render_reasoning(self): - pass \ No newline at end of file + pass diff --git a/test/onair/src/ai_components/test_planners_interface.py b/test/onair/src/ai_components/test_planners_interface.py index bae6fc7d..032b9b86 100644 --- a/test/onair/src/ai_components/test_planners_interface.py +++ b/test/onair/src/ai_components/test_planners_interface.py @@ -96,119 +96,4 @@ def test_PlannersInterfacee__init__sets_instance_ai_constructs_to_a_list_of_the_ assert cut.ai_constructs == expected_ai_constructs -# update tests -def test_PlannersInterface_update_only_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_when_instance_ai_constructs_is_empty(mocker): - # Arrange - arg_curr_data = MagicMock() - arg_status = MagicMock() - - mocker.patch(planners_interface.__name__ + '.status_to_oneHot') - - cut = PlannersInterface.__new__(PlannersInterface) - cut.ai_constructs = [] - - # Act - result = cut.update(arg_curr_data, arg_status) - - # Assert - assert planners_interface.status_to_oneHot.call_count == 1 - assert planners_interface.status_to_oneHot.call_args_list[0].args == (arg_status,) - assert result == None - -def test_PlannersInterface_update_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_and_calls_update_on_each_ai_construct_with_input_data_when_instance_ai_constructs_is_occupied(mocker): - # Arrange - arg_curr_data = MagicMock() - arg_status = MagicMock() - - mocker.patch(planners_interface.__name__ + '.status_to_oneHot') - - cut = PlannersInterface.__new__(PlannersInterface) - cut.ai_constructs = [] - - num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) - for i in range(num_fake_ai_constructs): - fake_ai_construct = MagicMock() - cut.ai_constructs.append(fake_ai_construct) - mocker.patch.object(fake_ai_construct, 'update') - - # Act - result = cut.update(arg_curr_data, arg_status) - - # Assert - assert planners_interface.status_to_oneHot.call_count == 1 - assert planners_interface.status_to_oneHot.call_args_list[0].args == (arg_status,) - for i in range(num_fake_ai_constructs): - assert cut.ai_constructs[i].update.call_count == 1 - assert cut.ai_constructs[i].update.call_args_list[0].args == (arg_curr_data,) - assert result == None - -# apriori_training tests -def test_PlannersInterface_apriori_training_does_nothing_when_instance_ai_constructs_is_empty(): - # Arrange - arg_batch_data = MagicMock() - - cut = PlannersInterface.__new__(PlannersInterface) - cut.ai_constructs = [] - - # Act - result = cut.apriori_training(arg_batch_data) - - # Assert - assert result == None -def test_PlannersInterface_apriori_training_calls_apriori_training_on_each_ai_constructs_item(mocker): - # Arrange - arg_batch_data = MagicMock() - - cut = PlannersInterface.__new__(PlannersInterface) - cut.ai_constructs = [] - - num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) - for i in range(num_fake_ai_constructs): - cut.ai_constructs.append(MagicMock()) - - # Act - result = cut.apriori_training(arg_batch_data) - - # Assert - for i in range(num_fake_ai_constructs): - assert cut.ai_constructs[i].apriori_training.call_count == 1 - assert cut.ai_constructs[i].apriori_training.call_args_list[0].args == (arg_batch_data, ) - assert result == None - -# render_reasoning tests -def test_PlannersInterface_render_reasoning_returns_empty_dict_when_instance_ai_constructs_is_empty(mocker): - # Arrange - cut = PlannersInterface.__new__(PlannersInterface) - cut.ai_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_ai_constructs_is_occupied(mocker): - # Arrange - cut = PlannersInterface.__new__(PlannersInterface) - cut.ai_constructs = [] - - expected_result = {} - - num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) - for i in range(num_fake_ai_constructs): - fake_ai_construct = MagicMock() - forced_return_ai_construct_render_reasoning = MagicMock() - cut.ai_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_ai_constructs): - assert cut.ai_constructs[i].render_reasoning.call_count == 1 - assert cut.ai_constructs[i].render_reasoning.call_args_list[0].args == () - assert result == expected_result \ No newline at end of file From a7087448a3a879ad8ef6fdc8b5dd0d6f1dd50059 Mon Sep 17 00:00:00 2001 From: Alan Gibson Date: Fri, 6 Oct 2023 16:59:17 -0400 Subject: [PATCH 11/18] Added tests to cover pass only methods --- .../ai_components/test_learners_interface.py | 11 +++++++ .../ai_components/test_planners_interface.py | 33 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/test/onair/src/ai_components/test_learners_interface.py b/test/onair/src/ai_components/test_learners_interface.py index 9bd737e4..516a2644 100644 --- a/test/onair/src/ai_components/test_learners_interface.py +++ b/test/onair/src/ai_components/test_learners_interface.py @@ -178,6 +178,17 @@ def test_LearnersInterface_apriori_training_calls_apriori_training_on_each_ai_co assert cut.ai_constructs[i].apriori_training.call_args_list[0].args == (arg_batch_data, ) assert result == None +# check_for_salient_event +def test_salient_event_does_nothing(): + # Arrange + cut = LearnersInterface.__new__(LearnersInterface) + + # Act + result = cut.check_for_salient_event() + + # Assert + assert result == None + # render_reasoning tests def test_LearnersInterface_render_reasoning_returns_empty_dict_when_instance_ai_constructs_is_empty(mocker): # Arrange diff --git a/test/onair/src/ai_components/test_planners_interface.py b/test/onair/src/ai_components/test_planners_interface.py index 032b9b86..e95ef49e 100644 --- a/test/onair/src/ai_components/test_planners_interface.py +++ b/test/onair/src/ai_components/test_planners_interface.py @@ -96,4 +96,37 @@ def test_PlannersInterfacee__init__sets_instance_ai_constructs_to_a_list_of_the_ assert cut.ai_constructs == expected_ai_constructs +def test_update_does_nothing(): + # Arrange + arg_curr_raw_tlm = MagicMock() + arg_status = MagicMock() + + cut = PlannersInterface.__new__(PlannersInterface) + + # Act + result = cut.update(arg_curr_raw_tlm, arg_status) + + # Assert + assert result == None + +def test_check_for_salient_event_does_nothing(): + # Arrange + cut = PlannersInterface.__new__(PlannersInterface) + + # Act + result = cut.check_for_salient_event() + + # Assert + assert result == None + +def test_render_reasoning_does_nothing(): + # Arrange + cut = PlannersInterface.__new__(PlannersInterface) + + # Act + result = cut.render_reasoning() + + # Assert + assert result == None + From 4e7b0ec2d774d0fd7084446755e7cd32c0cb50ad Mon Sep 17 00:00:00 2001 From: Evana Gizzi Date: Thu, 5 Oct 2023 14:58:11 -0400 Subject: [PATCH 12/18] added a planning intrface and renamed the DDL interface to learners. Now we have planners and learners. Closes #50 --- .../__init__.py | 0 .../ai_plugin_abstract/__init__.py | 0 .../ai_plugin_abstract/core.py | 0 .../learners_interface.py} | 2 +- onair/src/ai_components/planners_interface.py | 46 ++++++++++++++++ onair/src/reasoning/agent.py | 4 +- plugins/generic/generic_plugin.py | 2 +- plugins/kalman_plugin/kalman_plugin.py | 2 +- .../ai_plugin_abstract/test_core.py | 4 +- .../test_learners_interface.py} | 52 +++++++++---------- test/onair/src/reasoning/test_agent.py | 6 +-- 11 files changed, 82 insertions(+), 36 deletions(-) rename onair/src/{data_driven_components => ai_components}/__init__.py (100%) rename onair/src/{data_driven_components => ai_components}/ai_plugin_abstract/__init__.py (100%) rename onair/src/{data_driven_components => ai_components}/ai_plugin_abstract/core.py (100%) rename onair/src/{data_driven_components/data_driven_learning.py => ai_components/learners_interface.py} (98%) create mode 100644 onair/src/ai_components/planners_interface.py rename test/onair/src/{data_driven_components => ai_components}/ai_plugin_abstract/test_core.py (96%) rename test/onair/src/{data_driven_components/test_data_driven_learning.py => ai_components/test_learners_interface.py} (70%) diff --git a/onair/src/data_driven_components/__init__.py b/onair/src/ai_components/__init__.py similarity index 100% rename from onair/src/data_driven_components/__init__.py rename to onair/src/ai_components/__init__.py diff --git a/onair/src/data_driven_components/ai_plugin_abstract/__init__.py b/onair/src/ai_components/ai_plugin_abstract/__init__.py similarity index 100% rename from onair/src/data_driven_components/ai_plugin_abstract/__init__.py rename to onair/src/ai_components/ai_plugin_abstract/__init__.py diff --git a/onair/src/data_driven_components/ai_plugin_abstract/core.py b/onair/src/ai_components/ai_plugin_abstract/core.py similarity index 100% rename from onair/src/data_driven_components/ai_plugin_abstract/core.py rename to onair/src/ai_components/ai_plugin_abstract/core.py diff --git a/onair/src/data_driven_components/data_driven_learning.py b/onair/src/ai_components/learners_interface.py similarity index 98% rename from onair/src/data_driven_components/data_driven_learning.py rename to onair/src/ai_components/learners_interface.py index 4a37d491..d6677b33 100644 --- a/onair/src/data_driven_components/data_driven_learning.py +++ b/onair/src/ai_components/learners_interface.py @@ -15,7 +15,7 @@ from ..util.data_conversion import * -class DataDrivenLearning: +class LearnersInterface: def __init__(self, headers, _ai_plugins={}): assert(len(headers)>0), 'Headers are required' self.headers = headers diff --git a/onair/src/ai_components/planners_interface.py b/onair/src/ai_components/planners_interface.py new file mode 100644 index 00000000..80019ee4 --- /dev/null +++ b/onair/src/ai_components/planners_interface.py @@ -0,0 +1,46 @@ +# GSC-19165-1, "The On-Board Artificial Intelligence Research (OnAIR) Platform" +# +# Copyright © 2023 United States Government as represented by the Administrator of +# the National Aeronautics and Space Administration. No copyright is claimed in the +# United States under Title 17, U.S. Code. All Other Rights Reserved. +# +# Licensed under the NASA Open Source Agreement version 1.3 +# See "NOSA GSC-19165-1 OnAIR.pdf" + +""" +Planners interface class for managing all planning-based AI components +""" +import importlib.util +import importlib.util + +from ..util.data_conversion import * + +class PlannersInterface: + def __init__(self, headers, _ai_plugins={}): + assert(len(headers)>0), 'Headers are required' + self.headers = headers + self.planning_constructs = [] + for module_name in list(_planning_plugins.keys()): + spec = importlib.util.spec_from_file_location(module_name, _planning_plugins[module_name]) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + self.planning_constructs.append(module.Plugin(module_name,headers)) + + def update(self, curr_data, status): + input_data = curr_data + output_data = status_to_oneHot(status) + for plugin in self.planning_constructs: + plugin.update(input_data) + + def apriori_training(self, batch_data): + for plugin in self.planning_constructs: + plugin.apriori_training(batch_data) + + def render_reasoning(self): + diagnoses = {} + for plugin in self.planning_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 6a28a16d..83dcde33 100644 --- a/onair/src/reasoning/agent.py +++ b/onair/src/reasoning/agent.py @@ -11,13 +11,13 @@ Agent Class Deals with supervised learning for diagnosing statuses """ -from ..data_driven_components.data_driven_learning import DataDrivenLearning +from ..ai_components.learners_interface import LearnersInterface from ..reasoning.diagnosis import Diagnosis class Agent: def __init__(self, vehicle, plugin_list): self.vehicle_rep = vehicle - self.learning_systems = DataDrivenLearning(self.vehicle_rep.get_headers(),plugin_list) + self.learning_systems = LearnersInterface(self.vehicle_rep.get_headers(),plugin_list) self.mission_status = self.vehicle_rep.get_status() self.bayesian_status = self.vehicle_rep.get_bayesian_status() diff --git a/plugins/generic/generic_plugin.py b/plugins/generic/generic_plugin.py index 1b0da3ae..6a8a9acd 100644 --- a/plugins/generic/generic_plugin.py +++ b/plugins/generic/generic_plugin.py @@ -8,7 +8,7 @@ # See "NOSA GSC-19165-1 OnAIR.pdf" import numpy as np -from onair.src.data_driven_components.ai_plugin_abstract.core import AIPlugIn +from onair.src.ai_components.ai_plugin_abstract.core import AIPlugIn class Plugin(AIPlugIn): def apriori_training(self,batch_data=[]): diff --git a/plugins/kalman_plugin/kalman_plugin.py b/plugins/kalman_plugin/kalman_plugin.py index 7a557361..f97bb62a 100644 --- a/plugins/kalman_plugin/kalman_plugin.py +++ b/plugins/kalman_plugin/kalman_plugin.py @@ -9,7 +9,7 @@ import simdkalman import numpy as np -from onair.src.data_driven_components.ai_plugin_abstract.core import AIPlugIn +from onair.src.ai_components.ai_plugin_abstract.core import AIPlugIn class Plugin(AIPlugIn): def __init__(self, name, headers, window_size=3): diff --git a/test/onair/src/data_driven_components/ai_plugin_abstract/test_core.py b/test/onair/src/ai_components/ai_plugin_abstract/test_core.py similarity index 96% rename from test/onair/src/data_driven_components/ai_plugin_abstract/test_core.py rename to test/onair/src/ai_components/ai_plugin_abstract/test_core.py index 7667b5fb..ec7eb6ea 100644 --- a/test/onair/src/data_driven_components/ai_plugin_abstract/test_core.py +++ b/test/onair/src/ai_components/ai_plugin_abstract/test_core.py @@ -11,8 +11,8 @@ import pytest from mock import MagicMock -import onair.src.data_driven_components.ai_plugin_abstract.core as core -from onair.src.data_driven_components.ai_plugin_abstract.core import AIPlugIn +import onair.src.ai_components.ai_plugin_abstract.core as core +from onair.src.ai_components.ai_plugin_abstract.core import AIPlugIn class FakeAIPlugIn(AIPlugIn): def __init__(self, _name, _headers): diff --git a/test/onair/src/data_driven_components/test_data_driven_learning.py b/test/onair/src/ai_components/test_learners_interface.py similarity index 70% rename from test/onair/src/data_driven_components/test_data_driven_learning.py rename to test/onair/src/ai_components/test_learners_interface.py index d0054cc3..45b9df14 100644 --- a/test/onair/src/data_driven_components/test_data_driven_learning.py +++ b/test/onair/src/ai_components/test_learners_interface.py @@ -7,18 +7,18 @@ # Licensed under the NASA Open Source Agreement version 1.3 # See "NOSA GSC-19165-1 OnAIR.pdf" -""" Test DataDrivenLearning Functionality """ +""" Test LearnersInterface Functionality """ import pytest from mock import MagicMock -import onair.src.data_driven_components.data_driven_learning as data_driven_learning -from onair.src.data_driven_components.data_driven_learning import DataDrivenLearning +import onair.src.ai_components.learners_interface as learners_interface +from onair.src.ai_components.learners_interface import LearnersInterface import importlib from typing import Dict # __init__ tests -def test_DataDrivenLearning__init__sets_instance_headers_to_given_headers_and_does_nothing_else_when_given__ai_plugins_is_empty(mocker): +def test_LearnersInterface__init__sets_instance_headers_to_given_headers_and_does_nothing_else_when_given__ai_plugins_is_empty(mocker): # Arrange arg_headers = [] arg__ai_plugins = {} @@ -27,7 +27,7 @@ def test_DataDrivenLearning__init__sets_instance_headers_to_given_headers_and_do for i in range(num_fake_headers): arg_headers.append(MagicMock()) - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) # Act cut.__init__(arg_headers, arg__ai_plugins) @@ -35,7 +35,7 @@ def test_DataDrivenLearning__init__sets_instance_headers_to_given_headers_and_do # Assert assert cut.headers == arg_headers -def test_DataDrivenLearning__init__throws_AttributeError_when_given_module_file_has_no_attribute_Plugin(mocker): +def test_LearnersInterface__init__throws_AttributeError_when_given_module_file_has_no_attribute_Plugin(mocker): # Arrange fake_module_name = MagicMock() arg_headers = [] @@ -48,7 +48,7 @@ def test_DataDrivenLearning__init__throws_AttributeError_when_given_module_file_ # Assert -def test_DataDrivenLearning__init__sets_instance_ai_constructs_to_a_list_of_the_calls_AIPlugIn_with_plugin_and_given_headers_for_each_item_in_given__ai_plugins_when_given__ai_plugins_is_occupied(mocker): +def test_LearnersInterface__init__sets_instance_ai_constructs_to_a_list_of_the_calls_AIPlugIn_with_plugin_and_given_headers_for_each_item_in_given__ai_plugins_when_given__ai_plugins_is_occupied(mocker): # Arrange arg_headers = [] arg__ai_plugins = {} @@ -77,7 +77,7 @@ def test_DataDrivenLearning__init__sets_instance_ai_constructs_to_a_list_of_the_ for i, module in enumerate(fake_module_list): mocker.patch.object(module,'Plugin',return_value=expected_ai_constructs[i]) - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) # Act cut.__init__(arg_headers, arg__ai_plugins) @@ -99,32 +99,32 @@ def test_DataDrivenLearning__init__sets_instance_ai_constructs_to_a_list_of_the_ assert cut.ai_constructs == expected_ai_constructs # update tests -def test_DataDrivenLearning_update_only_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_when_instance_ai_constructs_is_empty(mocker): +def test_LearnersInterface_update_only_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_when_instance_ai_constructs_is_empty(mocker): # Arrange arg_curr_data = MagicMock() arg_status = MagicMock() - mocker.patch(data_driven_learning.__name__ + '.status_to_oneHot') + mocker.patch(learners_interface.__name__ + '.status_to_oneHot') - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) cut.ai_constructs = [] # Act result = cut.update(arg_curr_data, arg_status) # Assert - assert data_driven_learning.status_to_oneHot.call_count == 1 - assert data_driven_learning.status_to_oneHot.call_args_list[0].args == (arg_status,) + assert learners_interface.status_to_oneHot.call_count == 1 + assert learners_interface.status_to_oneHot.call_args_list[0].args == (arg_status,) assert result == None -def test_DataDrivenLearning_update_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_and_calls_update_on_each_ai_construct_with_input_data_when_instance_ai_constructs_is_occupied(mocker): +def test_LearnersInterface_update_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_and_calls_update_on_each_ai_construct_with_input_data_when_instance_ai_constructs_is_occupied(mocker): # Arrange arg_curr_data = MagicMock() arg_status = MagicMock() - mocker.patch(data_driven_learning.__name__ + '.status_to_oneHot') + mocker.patch(learners_interface.__name__ + '.status_to_oneHot') - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) cut.ai_constructs = [] num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) @@ -137,19 +137,19 @@ def test_DataDrivenLearning_update_calls_flotify_input_with_given_curr_data_and_ result = cut.update(arg_curr_data, arg_status) # Assert - assert data_driven_learning.status_to_oneHot.call_count == 1 - assert data_driven_learning.status_to_oneHot.call_args_list[0].args == (arg_status,) + assert learners_interface.status_to_oneHot.call_count == 1 + assert learners_interface.status_to_oneHot.call_args_list[0].args == (arg_status,) for i in range(num_fake_ai_constructs): assert cut.ai_constructs[i].update.call_count == 1 assert cut.ai_constructs[i].update.call_args_list[0].args == (arg_curr_data,) assert result == None # apriori_training tests -def test_DataDrivenLearning_apriori_training_does_nothing_when_instance_ai_constructs_is_empty(): +def test_LearnersInterface_apriori_training_does_nothing_when_instance_ai_constructs_is_empty(): # Arrange arg_batch_data = MagicMock() - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) cut.ai_constructs = [] # Act @@ -158,11 +158,11 @@ def test_DataDrivenLearning_apriori_training_does_nothing_when_instance_ai_const # Assert assert result == None -def test_DataDrivenLearning_apriori_training_calls_apriori_training_on_each_ai_constructs_item(mocker): +def test_LearnersInterface_apriori_training_calls_apriori_training_on_each_ai_constructs_item(mocker): # Arrange arg_batch_data = MagicMock() - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) cut.ai_constructs = [] num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) @@ -179,9 +179,9 @@ def test_DataDrivenLearning_apriori_training_calls_apriori_training_on_each_ai_c assert result == None # render_reasoning tests -def test_DataDrivenLearning_render_reasoning_returns_empty_dict_when_instance_ai_constructs_is_empty(mocker): +def test_LearnersInterface_render_reasoning_returns_empty_dict_when_instance_ai_constructs_is_empty(mocker): # Arrange - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) cut.ai_constructs = [] # Act @@ -190,9 +190,9 @@ def test_DataDrivenLearning_render_reasoning_returns_empty_dict_when_instance_ai # Assert assert result == {} -def test_DataDrivenLearning_render_reasoning_returns_dict_of_each_ai_construct_as_key_to_the_result_of_its_render_reasoning_when_instance_ai_constructs_is_occupied(mocker): +def test_LearnersInterface_render_reasoning_returns_dict_of_each_ai_construct_as_key_to_the_result_of_its_render_reasoning_when_instance_ai_constructs_is_occupied(mocker): # Arrange - cut = DataDrivenLearning.__new__(DataDrivenLearning) + cut = LearnersInterface.__new__(LearnersInterface) cut.ai_constructs = [] expected_result = {} diff --git a/test/onair/src/reasoning/test_agent.py b/test/onair/src/reasoning/test_agent.py index 5713ea06..cef204ce 100644 --- a/test/onair/src/reasoning/test_agent.py +++ b/test/onair/src/reasoning/test_agent.py @@ -26,7 +26,7 @@ def test_Agent__init__sets_vehicle_rep_to_given_vehicle_and_learning_systems_and fake_plugin_list = MagicMock() mocker.patch.object(arg_vehicle, 'get_headers', return_value=fake_headers) - mocker.patch(agent.__name__ + '.DataDrivenLearning', return_value=fake_learning_systems) + mocker.patch(agent.__name__ + '.LearnersInterface', return_value=fake_learning_systems) mocker.patch.object(arg_vehicle, 'get_status', return_value=fake_mission_status) mocker.patch.object(arg_vehicle, 'get_bayesian_status', return_value=fake_bayesian_status) @@ -39,8 +39,8 @@ def test_Agent__init__sets_vehicle_rep_to_given_vehicle_and_learning_systems_and assert cut.vehicle_rep == arg_vehicle assert arg_vehicle.get_headers.call_count == 1 assert arg_vehicle.get_headers.call_args_list[0].args == () - assert agent.DataDrivenLearning.call_count == 1 - assert agent.DataDrivenLearning.call_args_list[0].args == (fake_headers, fake_plugin_list) + assert agent.LearnersInterface.call_count == 1 + assert agent.LearnersInterface.call_args_list[0].args == (fake_headers, fake_plugin_list) assert cut.learning_systems == fake_learning_systems assert arg_vehicle.get_status.call_count == 1 assert arg_vehicle.get_status.call_args_list[0].args == () From b459c5e5d26c69cffe451afc21d48d9f4a91b549 Mon Sep 17 00:00:00 2001 From: Evana Gizzi Date: Thu, 5 Oct 2023 21:29:44 -0400 Subject: [PATCH 13/18] Needed to add a test_planner_interface suite --- onair/src/ai_components/planners_interface.py | 14 +- onair/src/reasoning/agent.py | 7 +- .../ai_components/test_planners_interface.py | 214 ++++++++++++++++++ test/onair/src/reasoning/test_agent.py | 11 +- 4 files changed, 237 insertions(+), 9 deletions(-) create mode 100644 test/onair/src/ai_components/test_planners_interface.py diff --git a/onair/src/ai_components/planners_interface.py b/onair/src/ai_components/planners_interface.py index 80019ee4..78a1666e 100644 --- a/onair/src/ai_components/planners_interface.py +++ b/onair/src/ai_components/planners_interface.py @@ -19,26 +19,26 @@ class PlannersInterface: def __init__(self, headers, _ai_plugins={}): assert(len(headers)>0), 'Headers are required' self.headers = headers - self.planning_constructs = [] - for module_name in list(_planning_plugins.keys()): - spec = importlib.util.spec_from_file_location(module_name, _planning_plugins[module_name]) + self.ai_constructs = [] + for module_name in list(_ai_plugins.keys()): + spec = importlib.util.spec_from_file_location(module_name, _ai_plugins[module_name]) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) - self.planning_constructs.append(module.Plugin(module_name,headers)) + self.ai_constructs.append(module.Plugin(module_name,headers)) def update(self, curr_data, status): input_data = curr_data output_data = status_to_oneHot(status) - for plugin in self.planning_constructs: + for plugin in self.ai_constructs: plugin.update(input_data) def apriori_training(self, batch_data): - for plugin in self.planning_constructs: + for plugin in self.ai_constructs: plugin.apriori_training(batch_data) def render_reasoning(self): diagnoses = {} - for plugin in self.planning_constructs: + for plugin in self.ai_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 83dcde33..2b827ced 100644 --- a/onair/src/reasoning/agent.py +++ b/onair/src/reasoning/agent.py @@ -12,20 +12,25 @@ Deals with supervised learning for diagnosing statuses """ from ..ai_components.learners_interface import LearnersInterface +from ..ai_components.planners_interface import PlannersInterface from ..reasoning.diagnosis import Diagnosis class Agent: def __init__(self, vehicle, plugin_list): self.vehicle_rep = vehicle - self.learning_systems = LearnersInterface(self.vehicle_rep.get_headers(),plugin_list) self.mission_status = self.vehicle_rep.get_status() self.bayesian_status = self.vehicle_rep.get_bayesian_status() + # AI Interfaces + self.learning_systems = LearnersInterface(self.vehicle_rep.get_headers(),plugin_list) + self.planning_systems = PlannersInterface(self.vehicle_rep.get_headers(),plugin_list) + # Markov Assumption holds def reason(self, frame): self.vehicle_rep.update(frame) self.mission_status = self.vehicle_rep.get_status() self.learning_systems.update(frame, self.mission_status) + self.planning_systems.update(frame, self.mission_status) def diagnose(self, time_step): """ Grab the mnemonics from the """ diff --git a/test/onair/src/ai_components/test_planners_interface.py b/test/onair/src/ai_components/test_planners_interface.py new file mode 100644 index 00000000..bae6fc7d --- /dev/null +++ b/test/onair/src/ai_components/test_planners_interface.py @@ -0,0 +1,214 @@ +# GSC-19165-1, "The On-Board Artificial Intelligence Research (OnAIR) Platform" +# +# Copyright © 2023 United States Government as represented by the Administrator of +# the National Aeronautics and Space Administration. No copyright is claimed in the +# United States under Title 17, U.S. Code. All Other Rights Reserved. +# +# Licensed under the NASA Open Source Agreement version 1.3 +# See "NOSA GSC-19165-1 OnAIR.pdf" + +""" Test PlannersInterface Functionality """ +import pytest +from mock import MagicMock + +import onair.src.ai_components.planners_interface as planners_interface +from onair.src.ai_components.planners_interface import PlannersInterface + +import importlib +from typing import Dict + +# __init__ tests +def test_PlannersInterface__init__sets_instance_headers_to_given_headers_and_does_nothing_else_when_given__ai_plugins_is_empty(mocker): + # Arrange + arg_headers = [] + arg__ai_plugins = {} + + num_fake_headers = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 headers (0 has own test) + for i in range(num_fake_headers): + arg_headers.append(MagicMock()) + + cut = PlannersInterface.__new__(PlannersInterface) + + # Act + cut.__init__(arg_headers, arg__ai_plugins) + + # Assert + assert cut.headers == arg_headers + +def test_PlannersInterface__init__throws_AttributeError_when_given_module_file_has_no_attribute_Plugin(mocker): + # Arrange + fake_module_name = MagicMock() + arg_headers = [] + + arg__ai_plugins = {MagicMock()} + + # Act + + # Assert + +def test_PlannersInterfacee__init__sets_instance_ai_constructs_to_a_list_of_the_calls_AIPlugIn_with_plugin_and_given_headers_for_each_item_in_given__ai_plugins_when_given__ai_plugins_is_occupied(mocker): + # Arrange + arg_headers = [] + arg__ai_plugins = {} + fake_spec_list = [] + fake_module_list = [] + + num_fake_headers = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 headers (0 has own test) + for i in range(num_fake_headers): + arg_headers.append(MagicMock()) + num_fake_ai_plugins = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) + for i in range(num_fake_ai_plugins): + arg__ai_plugins[str(MagicMock())] = str(MagicMock()) + fake_spec_list.append(MagicMock()) + fake_module_list.append(MagicMock()) + + + expected_ai_constructs = [] + for i in range(num_fake_ai_plugins): + expected_ai_constructs.append(MagicMock()) + + # mocker.patch('importlib.import_module', return_value=fake_imported_module) + mocker.patch('importlib.util.spec_from_file_location',side_effect=fake_spec_list) + mocker.patch('importlib.util.module_from_spec',side_effect=fake_module_list) + for spec in fake_spec_list: + mocker.patch.object(spec,'loader.exec_module') + for i, module in enumerate(fake_module_list): + mocker.patch.object(module,'Plugin',return_value=expected_ai_constructs[i]) + + cut = PlannersInterface.__new__(PlannersInterface) + + # Act + cut.__init__(arg_headers, arg__ai_plugins) + + # Assert + assert importlib.util.spec_from_file_location.call_count == len(arg__ai_plugins) + assert importlib.util.module_from_spec.call_count == len(fake_spec_list) + + for i in range(num_fake_ai_plugins): + fake_name = list(arg__ai_plugins.keys())[i] + fake_path = arg__ai_plugins[fake_name] + assert importlib.util.spec_from_file_location.call_args_list[i].args == (fake_name,fake_path) + assert importlib.util.module_from_spec.call_args_list[i].args == (fake_spec_list[i],) + assert fake_spec_list[i].loader.exec_module.call_count == 1 + assert fake_spec_list[i].loader.exec_module.call_args_list[0].args == (fake_module_list[i],) + assert fake_module_list[i].Plugin.call_count == 1 + assert fake_module_list[i].Plugin.call_args_list[0].args == (fake_name,arg_headers) + + assert cut.ai_constructs == expected_ai_constructs + +# update tests +def test_PlannersInterface_update_only_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_when_instance_ai_constructs_is_empty(mocker): + # Arrange + arg_curr_data = MagicMock() + arg_status = MagicMock() + + mocker.patch(planners_interface.__name__ + '.status_to_oneHot') + + cut = PlannersInterface.__new__(PlannersInterface) + cut.ai_constructs = [] + + # Act + result = cut.update(arg_curr_data, arg_status) + + # Assert + assert planners_interface.status_to_oneHot.call_count == 1 + assert planners_interface.status_to_oneHot.call_args_list[0].args == (arg_status,) + assert result == None + +def test_PlannersInterface_update_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_and_calls_update_on_each_ai_construct_with_input_data_when_instance_ai_constructs_is_occupied(mocker): + # Arrange + arg_curr_data = MagicMock() + arg_status = MagicMock() + + mocker.patch(planners_interface.__name__ + '.status_to_oneHot') + + cut = PlannersInterface.__new__(PlannersInterface) + cut.ai_constructs = [] + + num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) + for i in range(num_fake_ai_constructs): + fake_ai_construct = MagicMock() + cut.ai_constructs.append(fake_ai_construct) + mocker.patch.object(fake_ai_construct, 'update') + + # Act + result = cut.update(arg_curr_data, arg_status) + + # Assert + assert planners_interface.status_to_oneHot.call_count == 1 + assert planners_interface.status_to_oneHot.call_args_list[0].args == (arg_status,) + for i in range(num_fake_ai_constructs): + assert cut.ai_constructs[i].update.call_count == 1 + assert cut.ai_constructs[i].update.call_args_list[0].args == (arg_curr_data,) + assert result == None + +# apriori_training tests +def test_PlannersInterface_apriori_training_does_nothing_when_instance_ai_constructs_is_empty(): + # Arrange + arg_batch_data = MagicMock() + + cut = PlannersInterface.__new__(PlannersInterface) + cut.ai_constructs = [] + + # Act + result = cut.apriori_training(arg_batch_data) + + # Assert + assert result == None + +def test_PlannersInterface_apriori_training_calls_apriori_training_on_each_ai_constructs_item(mocker): + # Arrange + arg_batch_data = MagicMock() + + cut = PlannersInterface.__new__(PlannersInterface) + cut.ai_constructs = [] + + num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) + for i in range(num_fake_ai_constructs): + cut.ai_constructs.append(MagicMock()) + + # Act + result = cut.apriori_training(arg_batch_data) + + # Assert + for i in range(num_fake_ai_constructs): + assert cut.ai_constructs[i].apriori_training.call_count == 1 + assert cut.ai_constructs[i].apriori_training.call_args_list[0].args == (arg_batch_data, ) + assert result == None + +# render_reasoning tests +def test_PlannersInterface_render_reasoning_returns_empty_dict_when_instance_ai_constructs_is_empty(mocker): + # Arrange + cut = PlannersInterface.__new__(PlannersInterface) + cut.ai_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_ai_constructs_is_occupied(mocker): + # Arrange + cut = PlannersInterface.__new__(PlannersInterface) + cut.ai_constructs = [] + + expected_result = {} + + num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) + for i in range(num_fake_ai_constructs): + fake_ai_construct = MagicMock() + forced_return_ai_construct_render_reasoning = MagicMock() + cut.ai_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_ai_constructs): + assert cut.ai_constructs[i].render_reasoning.call_count == 1 + assert cut.ai_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 cef204ce..65c37aa2 100644 --- a/test/onair/src/reasoning/test_agent.py +++ b/test/onair/src/reasoning/test_agent.py @@ -21,12 +21,14 @@ def test_Agent__init__sets_vehicle_rep_to_given_vehicle_and_learning_systems_and fake_headers = MagicMock() fake_learning_systems = MagicMock() + fake_planning_systems = MagicMock() fake_mission_status = MagicMock() fake_bayesian_status = MagicMock() fake_plugin_list = MagicMock() mocker.patch.object(arg_vehicle, 'get_headers', return_value=fake_headers) mocker.patch(agent.__name__ + '.LearnersInterface', return_value=fake_learning_systems) + mocker.patch(agent.__name__ + '.PlannersInterface', return_value=fake_planning_systems) mocker.patch.object(arg_vehicle, 'get_status', return_value=fake_mission_status) mocker.patch.object(arg_vehicle, 'get_bayesian_status', return_value=fake_bayesian_status) @@ -37,11 +39,14 @@ def test_Agent__init__sets_vehicle_rep_to_given_vehicle_and_learning_systems_and # Assert assert cut.vehicle_rep == arg_vehicle - assert arg_vehicle.get_headers.call_count == 1 + assert arg_vehicle.get_headers.call_count == 2 assert arg_vehicle.get_headers.call_args_list[0].args == () assert agent.LearnersInterface.call_count == 1 assert agent.LearnersInterface.call_args_list[0].args == (fake_headers, fake_plugin_list) assert cut.learning_systems == fake_learning_systems + assert agent.PlannersInterface.call_count == 1 + assert agent.PlannersInterface.call_args_list[0].args == (fake_headers, fake_plugin_list) + assert cut.planning_systems == fake_planning_systems assert arg_vehicle.get_status.call_count == 1 assert arg_vehicle.get_status.call_args_list[0].args == () assert cut.mission_status == fake_mission_status @@ -59,6 +64,7 @@ def test_Agent_reason_updates_vehicle_rep_with_given_frame_and_sets_new_vehicle_ cut = Agent.__new__(Agent) cut.vehicle_rep = MagicMock() cut.learning_systems = MagicMock() + cut.planning_systems = MagicMock() mocker.patch.object(cut.vehicle_rep, 'update') mocker.patch.object(cut.vehicle_rep, 'get_status', return_value=fake_mission_status) @@ -74,6 +80,8 @@ def test_Agent_reason_updates_vehicle_rep_with_given_frame_and_sets_new_vehicle_ assert cut.vehicle_rep.get_status.call_args_list[0].args == () assert cut.learning_systems.update.call_count == 1 assert cut.learning_systems.update.call_args_list[0].args == (arg_frame, fake_mission_status) + assert cut.planning_systems.update.call_count == 1 + assert cut.planning_systems.update.call_args_list[0].args == (arg_frame, fake_mission_status) # diagnose tests def test_Agent_diagnose_returns_empty_Dict(): @@ -82,6 +90,7 @@ def test_Agent_diagnose_returns_empty_Dict(): cut = Agent.__new__(Agent) cut.learning_systems = MagicMock() + cut.planning_systems = MagicMock() cut.bayesian_status = MagicMock() cut.vehicle_rep = MagicMock() From 5bacff1b2fa08006001d0537157a3b1786b326e7 Mon Sep 17 00:00:00 2001 From: Evana Gizzi Date: Thu, 5 Oct 2023 21:31:58 -0400 Subject: [PATCH 14/18] removed extra lines and double import --- onair/src/ai_components/learners_interface.py | 4 ---- onair/src/ai_components/planners_interface.py | 4 ---- 2 files changed, 8 deletions(-) diff --git a/onair/src/ai_components/learners_interface.py b/onair/src/ai_components/learners_interface.py index d6677b33..76f4058b 100644 --- a/onair/src/ai_components/learners_interface.py +++ b/onair/src/ai_components/learners_interface.py @@ -11,7 +11,6 @@ Data driven learning class for managing all data driven AI components """ import importlib.util -import importlib.util from ..util.data_conversion import * @@ -41,6 +40,3 @@ def render_reasoning(self): for plugin in self.ai_constructs: diagnoses[plugin.component_name] = plugin.render_reasoning() return diagnoses - - - diff --git a/onair/src/ai_components/planners_interface.py b/onair/src/ai_components/planners_interface.py index 78a1666e..7de0483c 100644 --- a/onair/src/ai_components/planners_interface.py +++ b/onair/src/ai_components/planners_interface.py @@ -11,7 +11,6 @@ Planners interface class for managing all planning-based AI components """ import importlib.util -import importlib.util from ..util.data_conversion import * @@ -41,6 +40,3 @@ def render_reasoning(self): for plugin in self.ai_constructs: diagnoses[plugin.component_name] = plugin.render_reasoning() return diagnoses - - - From a147192eaf77af602a88b3586a4b8be0da336573 Mon Sep 17 00:00:00 2001 From: Evana Gizzi Date: Thu, 5 Oct 2023 21:43:40 -0400 Subject: [PATCH 15/18] Changed the planner interface a bit in order to handle the disambiguation of planners versus learners --- onair/src/ai_components/learners_interface.py | 9 ++++++--- onair/src/ai_components/planners_interface.py | 19 +++++++++---------- onair/src/reasoning/agent.py | 5 +++++ 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/onair/src/ai_components/learners_interface.py b/onair/src/ai_components/learners_interface.py index 76f4058b..b07c2327 100644 --- a/onair/src/ai_components/learners_interface.py +++ b/onair/src/ai_components/learners_interface.py @@ -25,15 +25,18 @@ def __init__(self, headers, _ai_plugins={}): spec.loader.exec_module(module) self.ai_constructs.append(module.Plugin(module_name,headers)) + def apriori_training(self, batch_data): + for plugin in self.ai_constructs: + plugin.apriori_training(batch_data) + def update(self, curr_data, status): input_data = curr_data output_data = status_to_oneHot(status) for plugin in self.ai_constructs: plugin.update(input_data) - def apriori_training(self, batch_data): - for plugin in self.ai_constructs: - plugin.apriori_training(batch_data) + def check_for_salient_event(self): + pass def render_reasoning(self): diagnoses = {} diff --git a/onair/src/ai_components/planners_interface.py b/onair/src/ai_components/planners_interface.py index 7de0483c..bc9a366d 100644 --- a/onair/src/ai_components/planners_interface.py +++ b/onair/src/ai_components/planners_interface.py @@ -25,18 +25,17 @@ def __init__(self, headers, _ai_plugins={}): spec.loader.exec_module(module) self.ai_constructs.append(module.Plugin(module_name,headers)) - def update(self, curr_data, status): - input_data = curr_data - output_data = status_to_oneHot(status) - for plugin in self.ai_constructs: - plugin.update(input_data) - def apriori_training(self, batch_data): for plugin in self.ai_constructs: plugin.apriori_training(batch_data) + def update(self, curr_raw_tlm): + # Raw TLM should be transformed into high-leve state representation here + # Can store something as stale unless a planning thread is launched + pass + + def check_for_salient_event(self): + pass + def render_reasoning(self): - diagnoses = {} - for plugin in self.ai_constructs: - diagnoses[plugin.component_name] = plugin.render_reasoning() - return diagnoses + pass \ No newline at end of file diff --git a/onair/src/reasoning/agent.py b/onair/src/reasoning/agent.py index 2b827ced..9b83a5ee 100644 --- a/onair/src/reasoning/agent.py +++ b/onair/src/reasoning/agent.py @@ -27,11 +27,16 @@ def __init__(self, vehicle, plugin_list): # Markov Assumption holds def reason(self, frame): + # Update with new telemetry self.vehicle_rep.update(frame) self.mission_status = self.vehicle_rep.get_status() self.learning_systems.update(frame, self.mission_status) self.planning_systems.update(frame, self.mission_status) + # Check for a salient event, needing acionable outcome + self.learning_systems.check_for_salient_event() + self.planning_systems.check_for_salient_event() + def diagnose(self, time_step): """ Grab the mnemonics from the """ learning_system_results = self.learning_systems.render_reasoning() From 4190a6198050ad6d5ee7ea19d75bee642b27486c Mon Sep 17 00:00:00 2001 From: Evana Gizzi Date: Thu, 5 Oct 2023 21:55:11 -0400 Subject: [PATCH 16/18] updated tests --- onair/src/ai_components/planners_interface.py | 8 +- .../ai_components/test_planners_interface.py | 115 ------------------ 2 files changed, 2 insertions(+), 121 deletions(-) diff --git a/onair/src/ai_components/planners_interface.py b/onair/src/ai_components/planners_interface.py index bc9a366d..f8339b86 100644 --- a/onair/src/ai_components/planners_interface.py +++ b/onair/src/ai_components/planners_interface.py @@ -24,12 +24,8 @@ def __init__(self, headers, _ai_plugins={}): module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) self.ai_constructs.append(module.Plugin(module_name,headers)) - - def apriori_training(self, batch_data): - for plugin in self.ai_constructs: - plugin.apriori_training(batch_data) - def update(self, curr_raw_tlm): + def update(self, curr_raw_tlm, status): # Raw TLM should be transformed into high-leve state representation here # Can store something as stale unless a planning thread is launched pass @@ -38,4 +34,4 @@ def check_for_salient_event(self): pass def render_reasoning(self): - pass \ No newline at end of file + pass diff --git a/test/onair/src/ai_components/test_planners_interface.py b/test/onair/src/ai_components/test_planners_interface.py index bae6fc7d..032b9b86 100644 --- a/test/onair/src/ai_components/test_planners_interface.py +++ b/test/onair/src/ai_components/test_planners_interface.py @@ -96,119 +96,4 @@ def test_PlannersInterfacee__init__sets_instance_ai_constructs_to_a_list_of_the_ assert cut.ai_constructs == expected_ai_constructs -# update tests -def test_PlannersInterface_update_only_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_when_instance_ai_constructs_is_empty(mocker): - # Arrange - arg_curr_data = MagicMock() - arg_status = MagicMock() - - mocker.patch(planners_interface.__name__ + '.status_to_oneHot') - - cut = PlannersInterface.__new__(PlannersInterface) - cut.ai_constructs = [] - - # Act - result = cut.update(arg_curr_data, arg_status) - - # Assert - assert planners_interface.status_to_oneHot.call_count == 1 - assert planners_interface.status_to_oneHot.call_args_list[0].args == (arg_status,) - assert result == None - -def test_PlannersInterface_update_calls_flotify_input_with_given_curr_data_and_status_to_oneHot_with_given_status_and_calls_update_on_each_ai_construct_with_input_data_when_instance_ai_constructs_is_occupied(mocker): - # Arrange - arg_curr_data = MagicMock() - arg_status = MagicMock() - - mocker.patch(planners_interface.__name__ + '.status_to_oneHot') - - cut = PlannersInterface.__new__(PlannersInterface) - cut.ai_constructs = [] - - num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) - for i in range(num_fake_ai_constructs): - fake_ai_construct = MagicMock() - cut.ai_constructs.append(fake_ai_construct) - mocker.patch.object(fake_ai_construct, 'update') - - # Act - result = cut.update(arg_curr_data, arg_status) - - # Assert - assert planners_interface.status_to_oneHot.call_count == 1 - assert planners_interface.status_to_oneHot.call_args_list[0].args == (arg_status,) - for i in range(num_fake_ai_constructs): - assert cut.ai_constructs[i].update.call_count == 1 - assert cut.ai_constructs[i].update.call_args_list[0].args == (arg_curr_data,) - assert result == None - -# apriori_training tests -def test_PlannersInterface_apriori_training_does_nothing_when_instance_ai_constructs_is_empty(): - # Arrange - arg_batch_data = MagicMock() - - cut = PlannersInterface.__new__(PlannersInterface) - cut.ai_constructs = [] - - # Act - result = cut.apriori_training(arg_batch_data) - - # Assert - assert result == None -def test_PlannersInterface_apriori_training_calls_apriori_training_on_each_ai_constructs_item(mocker): - # Arrange - arg_batch_data = MagicMock() - - cut = PlannersInterface.__new__(PlannersInterface) - cut.ai_constructs = [] - - num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) - for i in range(num_fake_ai_constructs): - cut.ai_constructs.append(MagicMock()) - - # Act - result = cut.apriori_training(arg_batch_data) - - # Assert - for i in range(num_fake_ai_constructs): - assert cut.ai_constructs[i].apriori_training.call_count == 1 - assert cut.ai_constructs[i].apriori_training.call_args_list[0].args == (arg_batch_data, ) - assert result == None - -# render_reasoning tests -def test_PlannersInterface_render_reasoning_returns_empty_dict_when_instance_ai_constructs_is_empty(mocker): - # Arrange - cut = PlannersInterface.__new__(PlannersInterface) - cut.ai_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_ai_constructs_is_occupied(mocker): - # Arrange - cut = PlannersInterface.__new__(PlannersInterface) - cut.ai_constructs = [] - - expected_result = {} - - num_fake_ai_constructs = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) - for i in range(num_fake_ai_constructs): - fake_ai_construct = MagicMock() - forced_return_ai_construct_render_reasoning = MagicMock() - cut.ai_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_ai_constructs): - assert cut.ai_constructs[i].render_reasoning.call_count == 1 - assert cut.ai_constructs[i].render_reasoning.call_args_list[0].args == () - assert result == expected_result \ No newline at end of file From 3adf4c554cdfda009cf74af8db3a49028c75a663 Mon Sep 17 00:00:00 2001 From: Alan Gibson Date: Fri, 6 Oct 2023 16:59:17 -0400 Subject: [PATCH 17/18] Added tests to cover pass only methods --- .../ai_components/test_learners_interface.py | 11 +++++++ .../ai_components/test_planners_interface.py | 33 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/test/onair/src/ai_components/test_learners_interface.py b/test/onair/src/ai_components/test_learners_interface.py index 45b9df14..4826b75b 100644 --- a/test/onair/src/ai_components/test_learners_interface.py +++ b/test/onair/src/ai_components/test_learners_interface.py @@ -178,6 +178,17 @@ def test_LearnersInterface_apriori_training_calls_apriori_training_on_each_ai_co assert cut.ai_constructs[i].apriori_training.call_args_list[0].args == (arg_batch_data, ) assert result == None +# check_for_salient_event +def test_salient_event_does_nothing(): + # Arrange + cut = LearnersInterface.__new__(LearnersInterface) + + # Act + result = cut.check_for_salient_event() + + # Assert + assert result == None + # render_reasoning tests def test_LearnersInterface_render_reasoning_returns_empty_dict_when_instance_ai_constructs_is_empty(mocker): # Arrange diff --git a/test/onair/src/ai_components/test_planners_interface.py b/test/onair/src/ai_components/test_planners_interface.py index 032b9b86..e95ef49e 100644 --- a/test/onair/src/ai_components/test_planners_interface.py +++ b/test/onair/src/ai_components/test_planners_interface.py @@ -96,4 +96,37 @@ def test_PlannersInterfacee__init__sets_instance_ai_constructs_to_a_list_of_the_ assert cut.ai_constructs == expected_ai_constructs +def test_update_does_nothing(): + # Arrange + arg_curr_raw_tlm = MagicMock() + arg_status = MagicMock() + + cut = PlannersInterface.__new__(PlannersInterface) + + # Act + result = cut.update(arg_curr_raw_tlm, arg_status) + + # Assert + assert result == None + +def test_check_for_salient_event_does_nothing(): + # Arrange + cut = PlannersInterface.__new__(PlannersInterface) + + # Act + result = cut.check_for_salient_event() + + # Assert + assert result == None + +def test_render_reasoning_does_nothing(): + # Arrange + cut = PlannersInterface.__new__(PlannersInterface) + + # Act + result = cut.render_reasoning() + + # Assert + assert result == None + From 2d843352fd1b641b0aff8d0c721c0b5aff3a6ffd Mon Sep 17 00:00:00 2001 From: Alan Gibson Date: Wed, 11 Oct 2023 15:22:54 -0400 Subject: [PATCH 18/18] Fixed planner tests Removed unused test Changed str(MagicMock()) Removed extra line feeds --- .../ai_components/test_planners_interface.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/test/onair/src/ai_components/test_planners_interface.py b/test/onair/src/ai_components/test_planners_interface.py index e95ef49e..c02d1787 100644 --- a/test/onair/src/ai_components/test_planners_interface.py +++ b/test/onair/src/ai_components/test_planners_interface.py @@ -35,18 +35,7 @@ def test_PlannersInterface__init__sets_instance_headers_to_given_headers_and_doe # Assert assert cut.headers == arg_headers -def test_PlannersInterface__init__throws_AttributeError_when_given_module_file_has_no_attribute_Plugin(mocker): - # Arrange - fake_module_name = MagicMock() - arg_headers = [] - - arg__ai_plugins = {MagicMock()} - - # Act - - # Assert - -def test_PlannersInterfacee__init__sets_instance_ai_constructs_to_a_list_of_the_calls_AIPlugIn_with_plugin_and_given_headers_for_each_item_in_given__ai_plugins_when_given__ai_plugins_is_occupied(mocker): +def test_PlannersInterface__init__sets_instance_ai_constructs_to_a_list_of_the_calls_AIPlugIn_with_plugin_and_given_headers_for_each_item_in_given__ai_plugins_when_given__ai_plugins_is_occupied(mocker): # Arrange arg_headers = [] arg__ai_plugins = {} @@ -58,11 +47,10 @@ def test_PlannersInterfacee__init__sets_instance_ai_constructs_to_a_list_of_the_ arg_headers.append(MagicMock()) num_fake_ai_plugins = pytest.gen.randint(1, 10) # arbitrary, from 1 to 10 (0 has own test) for i in range(num_fake_ai_plugins): - arg__ai_plugins[str(MagicMock())] = str(MagicMock()) + arg__ai_plugins[str(i)] = str(MagicMock()) fake_spec_list.append(MagicMock()) fake_module_list.append(MagicMock()) - expected_ai_constructs = [] for i in range(num_fake_ai_plugins): expected_ai_constructs.append(MagicMock()) @@ -128,5 +116,3 @@ def test_render_reasoning_does_nothing(): # Assert assert result == None - -