diff --git a/Tests/Test_Buffer.py b/Tests/Test_Buffer.py new file mode 100644 index 00000000..50d27889 --- /dev/null +++ b/Tests/Test_Buffer.py @@ -0,0 +1,133 @@ +import unittest + +from pynars import Narsese +from pynars.Config import Config +from pynars.NARS.DataStructures import EventBuffer +from pynars.Narsese import Judgement, Term, Task, Stamp, Base, Statement + + +class TEST_Buffer(unittest.TestCase): + + def test(self): + pass # todo add regular Buffer tests if needed + +class TEST_EventBuffer(unittest.TestCase): + + def test_3_firstorder_event_temporal_chaining(self): + """ + Add 3 first order events to the buffer (A,B,C), each with different timestamps (A=1, B=2, C=3) + + Ensure that the compound events are all created: + (A &/ B), (B &/ C), (A &/ C) + + Ensure that the implication statement is created: + ((A &/ B) =/> C) + """ + event_buffer: EventBuffer = EventBuffer(capacity=3) + + event_A_task: Task = Narsese.parser.parse("A2>.") + event_A_time = 0 + event_A_task.stamp.t_occurrence = event_A_time + + event_B_task: Task = Narsese.parser.parse("B2>.") + event_B_time = event_A_time + (Config.temporal_duration + 1) + event_B_task.stamp.t_occurrence = event_B_time + + event_C_task: Task = Narsese.parser.parse("C2>.") + event_C_time = event_B_time + (Config.temporal_duration + 1) + event_C_task.stamp.t_occurrence = event_C_time + + event_buffer.put(event_A_task) + event_buffer.put(event_B_task) + event_buffer.put(event_C_task) + results = event_buffer.generate_temporal_sentences() + + + A_and_B: Task = Narsese.parser.parse("(&/, A2>,+" + str(event_B_time - event_A_time) + ",B2>).") + + B_and_C: Task = Narsese.parser.parse("(&/, B2>,+" + str(event_C_time - event_B_time) + ",C2>).") + + A_and_C: Task = Narsese.parser.parse("(&/, A2>,+" + str(event_C_time - event_A_time) + ",C2>).") + + A_and_B_imply_C = Narsese.parser.parse("<(&/, A2>,+" + str(event_B_time - event_A_time) + ",B2>,+" + str( + event_C_time - event_B_time) + ") =/> C2>>.") + + expected_results = [A_and_B.term, + B_and_C.term, + A_and_C.term, + A_and_B_imply_C.term] + + for result in results: + self.assertTrue(result.term in expected_results,msg=str(result.term) + " was not found in results.") + expected_results.remove(result.term) + + + def test_buffer_overflow_maintains_capacity(self): + """ + Test to ensure the number of items in the buffer doesnt + exceed its upper bound + """ + capacity = 5 + event_buffer: EventBuffer = EventBuffer(capacity=capacity) + + # ensure the buffer adds events regularly + for i in range(capacity): + self.assertEqual(i, len(event_buffer)) + event_task = Narsese.parser.parse("A2>.") + event_task.stamp.t_occurrence = 1 + event_buffer.put(event_task) + + # ensure the buffer is at max capcity + self.assertEqual(capacity, len(event_buffer)) + + # ensure the buffer does not exceed its capacity when overflowing + event_task = Narsese.parser.parse("A2>.") + event_task.stamp.t_occurrence = 1 + event_buffer.put(event_task) + self.assertEqual(capacity, len(event_buffer)) + + def test_buffer_overflow_discards_older_events(self): + """ + Test to ensure that new events are added to the buffer, + whereas old events are purged from the buffer. + """ + capacity = 5 + event_buffer: EventBuffer = EventBuffer(capacity=capacity) + + for i in range(capacity): + event_task = Narsese.parser.parse("B" + str(i) + ">.") + event_task.stamp.t_occurrence = i + 1 + event_buffer.put(event_task) + + # ensure getting older/newer events functions properly + self.assertTrue(event_buffer.get_newest_event().stamp.t_occurrence > event_buffer.get_oldest_event().stamp.t_occurrence) + + # add an event older than all the others, ensure it doesnt get into the buffer + old_event_task = Narsese.parser.parse("oldB>.") + old_event_task.stamp.t_occurrence = 0 + event_buffer.put(old_event_task) + self.assertTrue(event_buffer.get_oldest_event().term != old_event_task.term) + + # add an event newer than all the others, ensure it goes to the front of the buffer + new_event_task = Narsese.parser.parse("newB>.") + new_event_task.stamp.t_occurrence = capacity + event_buffer.put(new_event_task) + self.assertTrue(event_buffer.get_newest_event().term == new_event_task.term) + +if __name__ == '__main__': + + test_classes_to_run = [ + TEST_EventBuffer + ] + + loader = unittest.TestLoader() + + suites = [] + for test_class in test_classes_to_run: + suite = loader.loadTestsFromTestCase(test_class) + suites.append(suite) + + suites = unittest.TestSuite(suites) + + runner = unittest.TextTestRunner() + results = runner.run(suites) \ No newline at end of file diff --git a/pynars/Config.py b/pynars/Config.py index 22cb8f78..93ac5a44 100644 --- a/pynars/Config.py +++ b/pynars/Config.py @@ -71,6 +71,12 @@ class Config: rate_discount_p_internal_exp = 0.1 rate_discount_d_internal_exp = 0.1 + # parameters for updating the Budget of Concept. + # Lower values means it is harder to change the budget, higher values means it is easier to change the budget + concept_update_durability_weight = 0.1 + concept_update_quality_weight = 0.1 + + # temporal parameters temporal_duration = 5 n_sequence_attempts = 10 n_op_condition_attempts = 10 diff --git a/pynars/NAL/Inference/LocalRules.py b/pynars/NAL/Inference/LocalRules.py index 29a14c50..5131eb50 100644 --- a/pynars/NAL/Inference/LocalRules.py +++ b/pynars/NAL/Inference/LocalRules.py @@ -43,7 +43,7 @@ def revision(task: Task, belief: Task, budget_tasklink: Budget=None, budget_term raise "Invalid case." return task -def solution_question(task: Task, belief: Belief, budget_tasklink: Budget=None, budget_termlink: Budget=None): +def solution_question(task: Task, belief: Task, budget_tasklink: Budget=None, budget_termlink: Budget=None): question: Union[Question, Quest] = task.sentence answer: Union[Judgement, Goal] = belief.sentence answer_best = question.best_answer diff --git a/pynars/NARS/Control/Reasoner.py b/pynars/NARS/Control/Reasoner.py index 8744089d..1f6123c4 100644 --- a/pynars/NARS/Control/Reasoner.py +++ b/pynars/NARS/Control/Reasoner.py @@ -13,7 +13,7 @@ from pynars.Narsese._py.Statement import Statement from pynars.Narsese._py.Task import Belief from pynars.Narsese import Copula -from ..DataStructures import Bag, Memory, NarseseChannel, Buffer, Task, Concept +from ..DataStructures import Bag, Memory, NarseseChannel, Buffer, Task, Concept, EventBuffer from ..InferenceEngine import GeneralEngine, TemporalEngine, VariableEngine, KanrenEngine from pynars import Config from pynars.Config import Enable @@ -57,6 +57,7 @@ def __init__(self, n_memory, capacity, config='./config.json', nal_rules={1, 2, self.memory = Memory(n_memory, global_eval=self.global_eval) self.overall_experience = Buffer(capacity) self.internal_experience = Buffer(capacity) + self.event_buffer = EventBuffer(3) self.narsese_channel = NarseseChannel(capacity) self.perception_channel = Channel(capacity) self.channels: List[Channel] = [ @@ -166,11 +167,17 @@ def observe(self, tasks_derived: List[Task]): Process Channels/Buffers """ judgement_revised, goal_revised, answers_question, answers_quest = None, None, None, None - # step 1. Take out an Item from `Channels`, and then put it into the `Overall Experience` + # step 1. Take out an Item from `Channels`, and then put it into the `Overall Experience` and Event Buffers for channel in self.channels: task_in: Task = channel.take() if task_in is not None: self.overall_experience.put(task_in) + if self.event_buffer.can_task_enter(task_in): + self.event_buffer.put(task_in) + # when there's a new event, run the temporal chaining + temporal_results = self.event_buffer.generate_temporal_sentences() + for result in temporal_results: + self.overall_experience.put(result) # step 2. Take out an Item from the `Internal Experience`, with putting it back afterwards, and then put it # into the `Overall Experience` diff --git a/pynars/NARS/DataStructures/_py/Buffer.py b/pynars/NARS/DataStructures/_py/Buffer.py index fd657e7c..b224ac88 100644 --- a/pynars/NARS/DataStructures/_py/Buffer.py +++ b/pynars/NARS/DataStructures/_py/Buffer.py @@ -1,8 +1,11 @@ +from pynars.NAL.Functions import Truth_intersection, Stamp_merge +from pynars.NAL.Inference.TemporalRules import induction_composition, induction_implication from .Bag import Bag from pynars.Config import Config -from pynars.Narsese import Item, Task +from pynars.Narsese import Item, Task, TermType, Compound, Interval, Statement from pynars.NAL.Functions.BudgetFunctions import * -from typing import Callable, Any +from typing import Callable, Any, List + class Buffer(Bag): ''' @@ -40,3 +43,79 @@ def __init__(self, capacity: int, n_buckets: int=None, take_in_order: bool=False def is_expired(self, put_time, current_time): return (current_time - put_time) > self.max_duration + + +class EventBuffer: + ''' + This buffer holds first-order events, sorted by time. + The purpose of this buffer is to generate temporal implication statements, e.g., (A &/ B =/> C) + and compound events, e.g., (A &/ B). + + The operation for generating temporal statements is exhaustive. That means, for generating 3-component + implication statements like (A &/ B =/> C), the algorithm scales O(n^3) for n elements + + The oldest events are at the lowest index, the newest events are at the highest index. + The larger the event's timestamp, the newer it is. + ''' + def __init__(self, capacity: int): + self.buffer: List[Task] = [] + self.capacity: int = capacity + + def __len__(self): + return len(self.buffer) + + def get_oldest_event(self): + return self.buffer[0] + + def get_newest_event(self): + return self.buffer[-1] + + def generate_temporal_sentences(self): + results: List[Task] = [] + # first event A occurred, then event B occurred, then event C + for i in range(len(self.buffer)): + event_A_task = self.buffer[i] + for j in range(i+1,len(self.buffer)): + # create (A &/ B) + event_B_task = self.buffer[j] + compound_event_task = induction_composition(event_A_task, event_B_task) + results.append(compound_event_task) # append + for k in range(j + 1, len(self.buffer)): + # create (A &/ B) =/> C + event_C = self.buffer[k] + temporal_implication_task = induction_implication(compound_event_task, event_C) + results.append(temporal_implication_task) # append + + return results + + def put(self, event_task_to_insert: Task): + if not self.can_task_enter(event_task_to_insert): + raise Exception("ERROR! Only events with first-order statements can enter the EventBuffer.") + + if len(self.buffer) == 0: # if nothing in the buffer, just insert it + self.buffer.append(event_task_to_insert) + return + + newest_event = self.get_newest_event() + + if event_task_to_insert.stamp.t_occurrence >= newest_event.stamp.t_occurrence: + # if its newer than even the newest event, just insert it at the end + self.buffer.append(event_task_to_insert) + else: + # otherwise, we have to go through the list to insert it properly + for i in range(len(self.buffer)): + buffer_event = self.buffer[i] + if event_task_to_insert.stamp.t_occurrence <= buffer_event.stamp.t_occurrence: + # the inserted event occurs first, so insert it here + self.buffer.insert(i, event_task_to_insert) + break + + + if len(self.buffer) > self.capacity: + # if too many events, take out the oldest event + self.buffer.pop(0) + + def can_task_enter(self, task: Task): + return task.is_event \ + and task.term.type == TermType.STATEMENT \ + and not task.term.is_higher_order \ No newline at end of file diff --git a/pynars/NARS/DataStructures/_py/Concept.py b/pynars/NARS/DataStructures/_py/Concept.py index 69819794..27c939d6 100644 --- a/pynars/NARS/DataStructures/_py/Concept.py +++ b/pynars/NARS/DataStructures/_py/Concept.py @@ -1,4 +1,6 @@ from typing import Tuple, Type, List, Union + +from pynars.NAL.Functions import Or from pynars.NAL.Functions.Tools import calculate_solution_quality, distribute_budget_among_links from pynars.NAL.Functions.BudgetFunctions import Budget_merge from pynars.Narsese import Belief, Task, Item, Budget, Sentence, Term, Task, Judgement, Goal @@ -150,12 +152,29 @@ def accept(self, task: Task, concepts: Bag=None, conceptualize: bool=True): if concept is None: return # The memroy is full, and the concept fails to get into the memory. self._build_task_links(concepts, task) self._build_term_links(concepts, task, budget) + + def update_priority(self, p, concepts: Bag): + concepts.take_by_key(key=self,remove=True) + self.budget.priority = Or(self.budget.priority, p) + concepts.put(item=self) + + def update_durability(self, d, concepts: Bag): + concepts.take_by_key(key=self, remove=True) + self.budget.durability = (Config.concept_update_durability_weight * d + + (1-Config.concept_update_durability_weight)*self.budget.durability) + concepts.put(item=self) + + def update_quality(self, q, concepts: Bag): + concepts.take_by_key(key=self, remove=True) + self.budget.quality = (Config.concept_update_quality_weight * q + + (1-Config.concept_update_quality_weight)*self.budget.quality) + concepts.put(item=self) def _build_task_links(self, concepts: Bag, task: Task): '''''' budget = task.budget task_link = TaskLink(self, task, budget, True, index=[]) - self._insert_task_link(task_link) + self._insert_task_link(task_link, concepts) if self.term.is_atom: return sub_budget = budget.distribute(self.term.count()-1) # TODO: It seems that the budget is not the same with that in OpenNARS 3.0.4/3.1.0. Check here. for term in self.term.components: @@ -166,7 +185,7 @@ def _build_task_links(self, concepts: Bag, task: Task): indices = Link.get_index(self.term, term) for index in indices: task_link = TaskLink(concept, task, sub_budget, index=index) - concept._insert_task_link(task_link) + concept._insert_task_link(task_link, concepts) def _build_term_links(self, concepts: Bag, task: Task, budget: Budget): ''' @@ -193,18 +212,24 @@ def _build_term_links(self, concepts: Bag, task: Task, budget: Budget): indices = Link.get_index(self.term, term) for index in indices: - self._insert_term_link(TermLink(self, sub_concept, sub_budget, False, index=index)) - sub_concept._insert_term_link(TermLink(sub_concept, self, sub_budget, True, index=index)) + self._insert_term_link(TermLink(self, sub_concept, sub_budget, False, index=index), concepts) + sub_concept._insert_term_link(TermLink(sub_concept, self, sub_budget, True, index=index), concepts) sub_concept._build_term_links(concepts, task, sub_budget) - def _insert_task_link(self, task_link: TaskLink): + def _insert_task_link(self, task_link: TaskLink, concepts: Bag): self.task_links.put(task_link) + # update the concept's budget using the link's budget + self.update_priority(task_link.budget.priority, concepts) + self.update_durability(task_link.budget.durability, concepts) # TODO: more handling. see OpenNARS 3.1.0 Concept.java line 318~366. - def _insert_term_link(self, term_link: TermLink): + def _insert_term_link(self, term_link: TermLink, concepts: Bag): self.term_links.put(term_link) + # update the concept's budget using the link's budget + self.update_priority(term_link.budget.priority, concepts) + self.update_durability(term_link.budget.durability, concepts) # TODO: more handling. see OpenNARS 3.1.0 Concept.java line 318~366. @classmethod diff --git a/pynars/NARS/DataStructures/_py/Link.py b/pynars/NARS/DataStructures/_py/Link.py index 53746401..51bc8220 100644 --- a/pynars/NARS/DataStructures/_py/Link.py +++ b/pynars/NARS/DataStructures/_py/Link.py @@ -187,12 +187,6 @@ def get_index(cls, main_term: Union[Term, Statement, Compound], sub_term: Union[ return indices - @classmethod - def update_budget(cls, budget: Budget, q: float, p_belief: float): - budget.priority = min(1.0, Or(budget.priority, Or(q, p_belief))) - budget.durability = min(1.0-Config.budget_epsilon, Or(budget.durability, q)) - - @property def is_valid(self): @@ -211,6 +205,11 @@ def set_type(self, source_is_component=True, type: LinkType=None): Link.set_type(self, source_is_component, type) if not self.is_valid: self.type = None + + def reward_budget(self, reward: float): + self.budget.quality = Or(self.budget.quality, reward) + + @property def is_valid(self): return self.type in ( @@ -240,12 +239,15 @@ def __init__(self, source: 'Concept', target: 'Concept', budget: Budget, copy_bu def set_type(self, source_is_component=True, type: LinkType=None): Link.set_type(self, source_is_component, type, enable_transform=True) if not self.is_valid: self.type = None + + def reward_budget(self, reward: float): + self.budget.priority = Or(self.budget.priority, reward) @property def is_valid(self) -> bool: return self.type in ( LinkType.SELF, - LinkType.COMPOUND, + LinkType.COMPOUND, LinkType.COMPOUND_STATEMENT, LinkType.COMPOUND_CONDITION, LinkType.TRANSFORM, diff --git a/pynars/NARS/DataStructures/_py/Memory.py b/pynars/NARS/DataStructures/_py/Memory.py index 32d87b1d..827fb9b9 100644 --- a/pynars/NARS/DataStructures/_py/Memory.py +++ b/pynars/NARS/DataStructures/_py/Memory.py @@ -1,24 +1,20 @@ -from pynars.NAL.Functions import BudgetFunctions - +from pynars import Global from pynars.Config import Enable, Config -from pynars.NAL.Inference.LocalRules import solve_query, solution_query, solution_question +from pynars.NAL.Functions.BudgetFunctions import Budget_evaluate_goal_solution +from pynars.NAL.Functions.Tools import calculate_solution_quality +from pynars.NAL.Functions.Tools import project, project_truth +from pynars.NAL.Functions.Tools import revisible +from pynars.NAL.Inference import local__revision +from pynars.NAL.Inference.LocalRules import solution_query, solution_question from pynars.NAL.MetaLevelInference.VariableSubstitution import get_elimination__var_const - from pynars.NARS.DataStructures._py.Link import TaskLink -from pynars.Narsese._py.Sentence import Goal, Judgement, Question -from pynars.Narsese import Statement, Term, Sentence, Budget, Task, Truth +from pynars.NARS.GlobalEval import GlobalEval +from pynars.Narsese import Statement, Budget, Task +from pynars.Narsese._py.Sentence import Goal, Question from pynars.Narsese._py.Task import Belief, Desire -from .Concept import Concept from .Bag import Bag -from pynars.NAL.Functions.Tools import revisible -from pynars.NAL.Inference import local__revision -# from pynars.NARS import Operation -from pynars.NAL.Functions.Tools import project, project_truth -from pynars import Global -from pynars.NAL.Functions.BudgetFunctions import Budget_evaluate_goal_solution -from pynars.NAL.Functions.Tools import calculate_solution_quality -from typing import Callable, Any -from pynars.NARS.GlobalEval import GlobalEval +from .Concept import Concept + class Memory: def __init__(self, capacity: int, n_buckets: int = None, take_in_order: bool = False, output_buffer = None, global_eval: GlobalEval=None) -> None: @@ -96,7 +92,7 @@ def accept(self, task: Task): def _accept_judgement(self, task: Task, concept: Concept): '''''' - belief_revised = None + belief_revised_task = None answers = { Question: [], Goal: [] } if Enable.operation: raise # InternalExperienceBuffer.handleOperationFeedback(task, nal); if Enable.anticipation: raise # ProcessAnticipation.confirmAnticipation(task, concept, nal); @@ -118,7 +114,7 @@ def _accept_judgement(self, task: Task, concept: Concept): } ''' raise - belief_revised = local__revision(task, belief) # TODO: handling the stamps + belief_revised_task: Task = local__revision(task, belief) # TODO: handling the stamps # reduce priority by achieving level task.reduce_budget_by_achieving_level(belief) @@ -140,7 +136,10 @@ def _accept_judgement(self, task: Task, concept: Concept): _, goal_answer = self._solve_goal(task_goal, concept, None, task) if goal_answer is not None: answers[Goal] = [goal_answer] - return belief_revised, answers + # Modify the concept's Budget using the belief + if belief_revised_task is not None: concept.update_quality(belief_revised_task.sentence.sharpness, concepts=self.concepts) + + return belief_revised_task, answers def _accept_question(self, task: Task, concept: Concept): '''''' @@ -296,7 +295,7 @@ def _accept_quest(self, task: Task, concept: Concept): return answers - def _solve_judgement(self, belief: Task, concept: Concept): + def _solve_judgement(self, belief_task: Task, concept: Concept): ''' It should be ensured that the task has no query-variables. @@ -306,11 +305,20 @@ def _solve_judgement(self, belief: Task, concept: Concept): ''' answers = [] # 1. try to solve yn-questions - for question in concept.question_table: - answer = solution_question(question, belief) + for question_task in concept.question_table: + question: Question = question_task.sentence + old_answer = question.best_answer + answer = solution_question(question_task, belief_task) + new_answer = question.best_answer + if old_answer != new_answer: + # the belief is a better answer to the question, so reward the Task and tasklinks + reward = question_task.achieving_level() + belief_task.reward_budget(reward) + for task_link in concept.task_links: + task_link.reward_budget(reward) if answer is not None: answers.append(answer) # 2. try to solve wh-questions - sub_terms = belief.term.sub_terms + sub_terms = belief_task.term.sub_terms for sub_term in sub_terms: concept_term: Concept = self.concepts.take_by_key(sub_term, remove=False) if concept_term is None: continue @@ -319,8 +327,8 @@ def _solve_judgement(self, belief: Task, concept: Concept): query = task_link.target if query is None: continue if not query.is_query: continue - if not query.term.equal(belief.term): continue - answer = solution_query(query, belief) + if not query.term.equal(belief_task.term): continue + answer = solution_query(query, belief_task) if answer is not None: answers.append(answer) return answers @@ -371,42 +379,47 @@ def _solve_query(self, query: Task, concept: Concept): raise "Invalid case." return answers - def _solve_goal(self, task: Task, concept: Concept, task_link: TaskLink=None, belief=None): + def _solve_goal(self, goal_task: Task, concept: Concept, task_link: TaskLink=None, belief_task: Task =None): ''' Args: - task (Task): Its sentence should be a goal. + goal_task (Task): Its sentence should be a goal. concept (Concept): The concept corresponding to the task. ''' tasks = [] - belief = belief or concept.match_belief(task.sentence) - if belief is None: - self.global_eval.update_satisfaction(task.achieving_level(), task.budget.priority) + belief_task = belief_task or concept.match_belief(goal_task.sentence) + if belief_task is None: + self.global_eval.update_satisfaction(goal_task.achieving_level(), goal_task.budget.priority) return tasks, None - old_best = task.best_solution + old_best = goal_task.best_solution - belief = belief or concept.match_belief(task.sentence) - if belief is None or belief == old_best: + belief_task = belief_task or concept.match_belief(goal_task.sentence) + if belief_task is None or belief_task == old_best: return tasks, None + elif belief_task != old_best: + reward = goal_task.achieving_level() + belief_task.reward_budget(reward) + for task_link in concept.task_links: + task_link.reward_budget(reward) if old_best is not None: - quality_new = calculate_solution_quality(task.sentence, belief.sentence, True) - quality_old = calculate_solution_quality(task.sentence, old_best.sentence, True) + quality_new = calculate_solution_quality(goal_task.sentence, belief_task.sentence, True) + quality_old = calculate_solution_quality(goal_task.sentence, old_best.sentence, True) if (quality_new <= quality_old): - return tasks, belief + return tasks, belief_task - task.best_solution = belief - tasks.append(belief) # the task as the new best solution should be added into the internal buffer, so that it would be paid attention - budget = Budget_evaluate_goal_solution(task.sentence, belief.sentence, task.budget, (task_link.budget if task_link is not None else None)) + goal_task.best_solution = belief_task + tasks.append(belief_task) # the task as the new best solution should be added into the internal buffer, so that it would be paid attention + budget = Budget_evaluate_goal_solution(goal_task.sentence, belief_task.sentence, goal_task.budget, (task_link.budget if task_link is not None else None)) if budget.is_above_thresh: - task.budget = budget - tasks.append(task) + goal_task.budget = budget + tasks.append(goal_task) ''' Here, belief is not None, and it is the best solution for the task Thus, do global evaluation to update satisfaction of the system. ''' - self.global_eval.update_satisfaction(task.achieving_level(belief.truth), task.budget.priority) + self.global_eval.update_satisfaction(goal_task.achieving_level(belief_task.truth), goal_task.budget.priority) - return tasks, belief + return tasks, belief_task def _solve_quest(self, task: Task, concept: Concept): ''' diff --git a/pynars/NARS/InferenceEngine/GeneralEngine/GeneralEngine.py b/pynars/NARS/InferenceEngine/GeneralEngine/GeneralEngine.py index 56eba53a..be361426 100644 --- a/pynars/NARS/InferenceEngine/GeneralEngine/GeneralEngine.py +++ b/pynars/NARS/InferenceEngine/GeneralEngine/GeneralEngine.py @@ -391,11 +391,14 @@ def step(self, concept: Concept): if is_valid: Global.States.record_premises(task, belief) Global.States.record_rules(rules) - tasks = self.inference(task, belief, term_belief, task_link_valid, term_link_valid, rules) - if term_link_valid is not None: # TODO: Check here whether the budget updating is the same as OpenNARS 3.0.4. - for task in tasks: TermLink.update_budget(term_link_valid.budget, task.budget.quality, belief.budget.priority if belief is not None else concept_target.budget.priority) - - tasks_derived.extend(tasks) + new_tasks = self.inference(task, belief, term_belief, task_link_valid, term_link_valid, rules) + if term_link_valid is not None: + # reward the termlink + for new_task in new_tasks: + reward: float = max(new_task.budget.priority, task.achieving_level()) + term_link_valid.reward_budget(reward) + + tasks_derived.extend(new_tasks) for term_link in term_links: concept.term_links.put_back(term_link) diff --git a/pynars/Narsese/_py/Sentence.py b/pynars/Narsese/_py/Sentence.py index 79147545..979fc8cf 100644 --- a/pynars/Narsese/_py/Sentence.py +++ b/pynars/Narsese/_py/Sentence.py @@ -106,8 +106,8 @@ def directness(self): @property def sharpness(self): - if self.truth_value is None: return None - else: return 2 * abs(self.truth_value.e - 0.5) + if self.truth is None: return None + else: return 2 * abs(self.truth.e - 0.5) # @property # def temporal_order(self): diff --git a/pynars/Narsese/_py/Task.py b/pynars/Narsese/_py/Task.py index 42ca02c6..1bbc8e6e 100644 --- a/pynars/Narsese/_py/Task.py +++ b/pynars/Narsese/_py/Task.py @@ -1,11 +1,16 @@ from copy import copy from typing import Type, Union + +from pynars import NAL + from .Sentence import Sentence, Judgement, Goal, Quest, Question, Stamp from .Item import Item from .Budget import Budget from .Term import Term from .Truth import Truth + + class Task(Item): input_id = -1 best_solution: 'Task' = None @@ -37,6 +42,9 @@ def achieving_level(self, previous_belief: Truth=None): else: raise f'Invalid type! {type(self.sentence)}' + def reward_budget(self, reward: float): + self.budget.priority = NAL.Functions.Or(self.budget.priority, reward) + def reduce_budget_by_achieving_level(self, belief_selected: Union[Type['Belief'], None]): truth = belief_selected.truth if belief_selected is not None else None self.budget.reduce_by_achieving_level(self.achieving_level(truth))