Skip to content

Commit

Permalink
Merge pull request nasa#53 from nasa/50-add-interface-to-planners
Browse files Browse the repository at this point in the history
added a planning interface and renamed the DDL interface to learners. …
  • Loading branch information
Evana13G authored Oct 11, 2023
2 parents d36a1ef + 010f1aa commit be2cc36
Show file tree
Hide file tree
Showing 12 changed files with 228 additions and 44 deletions.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@
Data driven learning class for managing all data driven AI components
"""
import importlib.util
import importlib.util

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
Expand All @@ -26,21 +25,21 @@ 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 = {}
for plugin in self.ai_constructs:
diagnoses[plugin.component_name] = plugin.render_reasoning()
return diagnoses



37 changes: 37 additions & 0 deletions onair/src/ai_components/planners_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 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

from ..util.data_conversion import *

class PlannersInterface:
def __init__(self, headers, _ai_plugins={}):
assert(len(headers)>0), 'Headers are required'
self.headers = headers
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.ai_constructs.append(module.Plugin(module_name,headers))

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

def check_for_salient_event(self):
pass

def render_reasoning(self):
pass
14 changes: 12 additions & 2 deletions onair/src/reasoning/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,31 @@
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 ..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 = DataDrivenLearning(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):
# 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 """
Expand Down
2 changes: 1 addition & 1 deletion plugins/generic/generic_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=[]):
Expand Down
2 changes: 1 addition & 1 deletion plugins/kalman_plugin/kalman_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand All @@ -27,15 +27,15 @@ 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)

# 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 = []
Expand All @@ -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 = {}
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -178,10 +178,21 @@ def test_DataDrivenLearning_apriori_training_calls_apriori_training_on_each_ai_c
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_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
Expand All @@ -190,9 +201,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 = {}
Expand Down
Loading

0 comments on commit be2cc36

Please sign in to comment.