From 04c16e0b1b3d8e50d089683fcdd8c5e2290677f5 Mon Sep 17 00:00:00 2001 From: ccrock4t <15344554+ccrock4t@users.noreply.github.com> Date: Tue, 16 Jan 2024 18:03:48 -0500 Subject: [PATCH 01/17] Add methods to update Budget of Concept --- pynars/Config.py | 7 +++++++ pynars/NARS/DataStructures/_py/Concept.py | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/pynars/Config.py b/pynars/Config.py index 22cb8f78..96fc0ae8 100644 --- a/pynars/Config.py +++ b/pynars/Config.py @@ -71,6 +71,13 @@ 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_priority_weight = 0.1 + concept_update_durability_weight = 0.1 + concept_update_quality_weight = 0.1 + + # temporal_duration = 5 n_sequence_attempts = 10 n_op_condition_attempts = 10 diff --git a/pynars/NARS/DataStructures/_py/Concept.py b/pynars/NARS/DataStructures/_py/Concept.py index 05dc1051..5a147f0d 100644 --- a/pynars/NARS/DataStructures/_py/Concept.py +++ b/pynars/NARS/DataStructures/_py/Concept.py @@ -138,6 +138,19 @@ 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): + self.budget.priority = (Config.concept_update_priority_weight * p + + (1-Config.concept_update_priority_weight)*self.budget.priority) + + def update_durability(self, d): + self.budget.durability = (Config.concept_update_durability_weight * d + + (1-Config.concept_update_durability_weight)*self.budget.durability) + + def update_quality(self, q): + self.budget.quality = (Config.concept_update_quality_weight * q + + (1-Config.concept_update_quality_weight)*self.budget.quality) def _build_task_links(self, concepts: Bag, task: Task): '''''' From 7059b6f7a625416006a8a269bb3ff3f774ef8395 Mon Sep 17 00:00:00 2001 From: ccrock4t <15344554+ccrock4t@users.noreply.github.com> Date: Wed, 17 Jan 2024 14:33:17 -0500 Subject: [PATCH 02/17] #67 Update the Concept's Quality with the Revised Belief's Sharpness whenever a judgment is accepted --- pynars/NARS/DataStructures/_py/Memory.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pynars/NARS/DataStructures/_py/Memory.py b/pynars/NARS/DataStructures/_py/Memory.py index 32d87b1d..9fd125c4 100644 --- a/pynars/NARS/DataStructures/_py/Memory.py +++ b/pynars/NARS/DataStructures/_py/Memory.py @@ -86,6 +86,9 @@ def accept(self, task: Task): # Build the concepts corresponding to the terms of those components within the task. concept.accept(task, self.concepts, conceptualize=False) + + + if Enable.temporal_reasoning or Enable.operation: # if (!task.sentence.isEternal() && !(task.sentence.term instanceof Operation)) { # globalBuffer.eventInference(task, cont, false); //can be triggered by Buffer itself in the future @@ -140,6 +143,9 @@ 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] + # Modify the concept's Budget using the belief + if belief_revised is not None: concept.update_quality(belief_revised.sharpness) + return belief_revised, answers def _accept_question(self, task: Task, concept: Concept): From acbb00dbec161a6db0f114769916814aef6fe5a2 Mon Sep 17 00:00:00 2001 From: ccrock4t <15344554+ccrock4t@users.noreply.github.com> Date: Wed, 17 Jan 2024 14:36:47 -0500 Subject: [PATCH 03/17] #68 #71 use links to update the concept budget --- pynars/Config.py | 1 - pynars/NARS/DataStructures/_py/Concept.py | 9 +++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pynars/Config.py b/pynars/Config.py index 96fc0ae8..24679f03 100644 --- a/pynars/Config.py +++ b/pynars/Config.py @@ -73,7 +73,6 @@ class Config: # 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_priority_weight = 0.1 concept_update_durability_weight = 0.1 concept_update_quality_weight = 0.1 diff --git a/pynars/NARS/DataStructures/_py/Concept.py b/pynars/NARS/DataStructures/_py/Concept.py index 5a147f0d..4c2fba45 100644 --- a/pynars/NARS/DataStructures/_py/Concept.py +++ b/pynars/NARS/DataStructures/_py/Concept.py @@ -141,8 +141,7 @@ def accept(self, task: Task, concepts: Bag=None, conceptualize: bool=True): def update_priority(self, p): - self.budget.priority = (Config.concept_update_priority_weight * p - + (1-Config.concept_update_priority_weight)*self.budget.priority) + self.budget.priority = Or(self.budget.priority, p) def update_durability(self, d): self.budget.durability = (Config.concept_update_durability_weight * d @@ -202,10 +201,16 @@ def _build_term_links(self, concepts: Bag, task: Task, budget: Budget): def _insert_task_link(self, task_link: TaskLink): self.task_links.put(task_link) + # update the concept's budget using the link's budget + self.update_priority(task_link.budget.priority) + self.update_durability(task_link.budget.durability) # TODO: more handling. see OpenNARS 3.1.0 Concept.java line 318~366. def _insert_term_link(self, term_link: TermLink): self.term_links.put(term_link) + # update the concept's budget using the link's budget + self.update_priority(term_link.budget.priority) + self.update_durability(term_link.budget.durability) # TODO: more handling. see OpenNARS 3.1.0 Concept.java line 318~366. @classmethod From 0fce5be0da4223aef33559964e7031130d212f62 Mon Sep 17 00:00:00 2001 From: ccrock4t <15344554+ccrock4t@users.noreply.github.com> Date: Wed, 17 Jan 2024 14:46:54 -0500 Subject: [PATCH 04/17] remove excessive whitespace --- pynars/Config.py | 2 +- pynars/NARS/DataStructures/_py/Concept.py | 1 - pynars/NARS/DataStructures/_py/Memory.py | 3 --- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/pynars/Config.py b/pynars/Config.py index 24679f03..93ac5a44 100644 --- a/pynars/Config.py +++ b/pynars/Config.py @@ -76,7 +76,7 @@ class Config: 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/NARS/DataStructures/_py/Concept.py b/pynars/NARS/DataStructures/_py/Concept.py index 4c2fba45..b1eac474 100644 --- a/pynars/NARS/DataStructures/_py/Concept.py +++ b/pynars/NARS/DataStructures/_py/Concept.py @@ -139,7 +139,6 @@ def accept(self, task: Task, concepts: Bag=None, conceptualize: bool=True): self._build_task_links(concepts, task) self._build_term_links(concepts, task, budget) - def update_priority(self, p): self.budget.priority = Or(self.budget.priority, p) diff --git a/pynars/NARS/DataStructures/_py/Memory.py b/pynars/NARS/DataStructures/_py/Memory.py index 9fd125c4..343b3e80 100644 --- a/pynars/NARS/DataStructures/_py/Memory.py +++ b/pynars/NARS/DataStructures/_py/Memory.py @@ -86,9 +86,6 @@ def accept(self, task: Task): # Build the concepts corresponding to the terms of those components within the task. concept.accept(task, self.concepts, conceptualize=False) - - - if Enable.temporal_reasoning or Enable.operation: # if (!task.sentence.isEternal() && !(task.sentence.term instanceof Operation)) { # globalBuffer.eventInference(task, cont, false); //can be triggered by Buffer itself in the future From 58a68fb2fc437a84ee27c9b7586fb1fc3abf6432 Mon Sep 17 00:00:00 2001 From: ccrock4t <15344554+ccrock4t@users.noreply.github.com> Date: Thu, 18 Jan 2024 03:46:48 -0500 Subject: [PATCH 05/17] Remove and replace Concept in Bag to properly change its level based on its updated budget --- pynars/NARS/DataStructures/_py/Memory.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pynars/NARS/DataStructures/_py/Memory.py b/pynars/NARS/DataStructures/_py/Memory.py index 343b3e80..46b0e673 100644 --- a/pynars/NARS/DataStructures/_py/Memory.py +++ b/pynars/NARS/DataStructures/_py/Memory.py @@ -45,6 +45,9 @@ def accept(self, task: Task): concept: Concept = Concept._conceptualize(self.concepts, task.term, conceptualize_budget) if concept is None: return None # The memroy is full. The concept fails to get into the memory. + # take out the concept, to update the budget + concept = self.concepts.take_by_key(key=task.term,remove=True) + # then process each task according to its type task_revised, goal_derived, answers_question, answer_quest = None, None, None, None if task.is_judgement: @@ -86,6 +89,9 @@ def accept(self, task: Task): # Build the concepts corresponding to the terms of those components within the task. concept.accept(task, self.concepts, conceptualize=False) + # put back the concept, to update the budget + self.concepts.put_back(item=concept,key=task.term) + if Enable.temporal_reasoning or Enable.operation: # if (!task.sentence.isEternal() && !(task.sentence.term instanceof Operation)) { # globalBuffer.eventInference(task, cont, false); //can be triggered by Buffer itself in the future From 40cc2cb6cc76ac93df3a4c2335a176cc5bb1f0d3 Mon Sep 17 00:00:00 2001 From: ccrock4t <15344554+ccrock4t@users.noreply.github.com> Date: Thu, 18 Jan 2024 15:49:58 -0500 Subject: [PATCH 06/17] Change item level properly when changing Concept budget --- pynars/NARS/DataStructures/_py/Concept.py | 34 ++++++++++++++--------- pynars/NARS/DataStructures/_py/Memory.py | 8 +++--- pynars/Narsese/_py/Sentence.py | 4 +-- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/pynars/NARS/DataStructures/_py/Concept.py b/pynars/NARS/DataStructures/_py/Concept.py index b1eac474..948f19bb 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 @@ -139,22 +141,28 @@ def accept(self, task: Task, concepts: Bag=None, conceptualize: bool=True): self._build_task_links(concepts, task) self._build_term_links(concepts, task, budget) - def update_priority(self, p): + 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): + 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): + 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: @@ -165,7 +173,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): ''' @@ -192,24 +200,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) - self.update_durability(task_link.budget.durability) + 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) - self.update_durability(term_link.budget.durability) + 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/Memory.py b/pynars/NARS/DataStructures/_py/Memory.py index 46b0e673..aeafd287 100644 --- a/pynars/NARS/DataStructures/_py/Memory.py +++ b/pynars/NARS/DataStructures/_py/Memory.py @@ -102,7 +102,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); @@ -124,7 +124,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) @@ -147,9 +147,9 @@ def _accept_judgement(self, task: Task, concept: Concept): if goal_answer is not None: answers[Goal] = [goal_answer] # Modify the concept's Budget using the belief - if belief_revised is not None: concept.update_quality(belief_revised.sharpness) + if belief_revised_task is not None: concept.update_quality(belief_revised_task.sentence.sharpness, concepts=self.concepts) - return belief_revised, answers + return belief_revised_task, answers def _accept_question(self, task: Task, concept: Concept): '''''' 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): From 86a75ed008fba16b7fea50e7b12feca09500b675 Mon Sep 17 00:00:00 2001 From: ccrock4t <15344554+ccrock4t@users.noreply.github.com> Date: Thu, 18 Jan 2024 15:56:38 -0500 Subject: [PATCH 07/17] Update Memory.py --- pynars/NARS/DataStructures/_py/Memory.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pynars/NARS/DataStructures/_py/Memory.py b/pynars/NARS/DataStructures/_py/Memory.py index aeafd287..1e348c99 100644 --- a/pynars/NARS/DataStructures/_py/Memory.py +++ b/pynars/NARS/DataStructures/_py/Memory.py @@ -45,9 +45,6 @@ def accept(self, task: Task): concept: Concept = Concept._conceptualize(self.concepts, task.term, conceptualize_budget) if concept is None: return None # The memroy is full. The concept fails to get into the memory. - # take out the concept, to update the budget - concept = self.concepts.take_by_key(key=task.term,remove=True) - # then process each task according to its type task_revised, goal_derived, answers_question, answer_quest = None, None, None, None if task.is_judgement: @@ -89,9 +86,6 @@ def accept(self, task: Task): # Build the concepts corresponding to the terms of those components within the task. concept.accept(task, self.concepts, conceptualize=False) - # put back the concept, to update the budget - self.concepts.put_back(item=concept,key=task.term) - if Enable.temporal_reasoning or Enable.operation: # if (!task.sentence.isEternal() && !(task.sentence.term instanceof Operation)) { # globalBuffer.eventInference(task, cont, false); //can be triggered by Buffer itself in the future From 2a15f53ae9baa29ba6d4a552e2c24c1ed006e118 Mon Sep 17 00:00:00 2001 From: ccrock4t <15344554+ccrock4t@users.noreply.github.com> Date: Tue, 30 Jan 2024 07:55:49 -0500 Subject: [PATCH 08/17] Update the Priority of a Judgment Task when it becomes the best answer to a Goal/Question --- pynars/NAL/Inference/LocalRules.py | 2 +- pynars/NARS/DataStructures/_py/Memory.py | 85 +++++++++++++----------- pynars/Narsese/_py/Task.py | 8 +++ 3 files changed, 54 insertions(+), 41 deletions(-) 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/DataStructures/_py/Memory.py b/pynars/NARS/DataStructures/_py/Memory.py index 1e348c99..fcd8a359 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: @@ -299,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. @@ -309,11 +305,17 @@ 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 it + belief_task.reward_budget_priority(question_task.achieving_level()) 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 @@ -322,8 +324,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 @@ -374,42 +376,45 @@ 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: + belief_task.reward_budget_priority(goal_task.achieving_level()) + 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/Narsese/_py/Task.py b/pynars/Narsese/_py/Task.py index 3aaeedef..d54e5f89 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 @@ -35,6 +40,9 @@ def achieving_level(self, previous_belief: Truth=None): else: raise f'Invalid type! {type(self.sentence)}' + def reward_budget_priority(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)) From f25257804e42fa589efb6570a3810ab2b7621b7f Mon Sep 17 00:00:00 2001 From: ccrock4t <15344554+ccrock4t@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:03:33 -0500 Subject: [PATCH 09/17] Reward termlinks for #75 --- pynars/NARS/DataStructures/_py/Link.py | 11 +++++------ .../InferenceEngine/GeneralEngine/GeneralEngine.py | 11 +++++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/pynars/NARS/DataStructures/_py/Link.py b/pynars/NARS/DataStructures/_py/Link.py index 53746401..6daaaf77 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 ( diff --git a/pynars/NARS/InferenceEngine/GeneralEngine/GeneralEngine.py b/pynars/NARS/InferenceEngine/GeneralEngine/GeneralEngine.py index 56eba53a..87b036f9 100644 --- a/pynars/NARS/InferenceEngine/GeneralEngine/GeneralEngine.py +++ b/pynars/NARS/InferenceEngine/GeneralEngine/GeneralEngine.py @@ -391,10 +391,13 @@ 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) - + derived_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 derived_task in derived_tasks: + reward: float = max(derived_task.budget.priority, task.achieving_level()) + TermLink.reward_budget(reward) + tasks_derived.extend(tasks) for term_link in term_links: concept.term_links.put_back(term_link) From 1e6dfd2cc213d40275dde855303cd02a40bc057e Mon Sep 17 00:00:00 2001 From: ccrock4t <15344554+ccrock4t@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:07:06 -0500 Subject: [PATCH 10/17] Update GeneralEngine.py --- .../InferenceEngine/GeneralEngine/GeneralEngine.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pynars/NARS/InferenceEngine/GeneralEngine/GeneralEngine.py b/pynars/NARS/InferenceEngine/GeneralEngine/GeneralEngine.py index 87b036f9..be361426 100644 --- a/pynars/NARS/InferenceEngine/GeneralEngine/GeneralEngine.py +++ b/pynars/NARS/InferenceEngine/GeneralEngine/GeneralEngine.py @@ -391,14 +391,14 @@ def step(self, concept: Concept): if is_valid: Global.States.record_premises(task, belief) Global.States.record_rules(rules) - derived_tasks = self.inference(task, belief, term_belief, task_link_valid, term_link_valid, rules) + 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 derived_task in derived_tasks: - reward: float = max(derived_task.budget.priority, task.achieving_level()) - TermLink.reward_budget(reward) + 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(tasks) + tasks_derived.extend(new_tasks) for term_link in term_links: concept.term_links.put_back(term_link) From 569883752e25043d6fc76bef369db15814e68cbe Mon Sep 17 00:00:00 2001 From: ccrock4t <15344554+ccrock4t@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:20:01 -0500 Subject: [PATCH 11/17] Reward tasklinks like tasks, for #77 --- pynars/NARS/DataStructures/_py/Link.py | 5 ++++- pynars/NARS/DataStructures/_py/Memory.py | 13 +++++++++---- pynars/Narsese/_py/Task.py | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/pynars/NARS/DataStructures/_py/Link.py b/pynars/NARS/DataStructures/_py/Link.py index 6daaaf77..51bc8220 100644 --- a/pynars/NARS/DataStructures/_py/Link.py +++ b/pynars/NARS/DataStructures/_py/Link.py @@ -239,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 fcd8a359..827fb9b9 100644 --- a/pynars/NARS/DataStructures/_py/Memory.py +++ b/pynars/NARS/DataStructures/_py/Memory.py @@ -311,8 +311,11 @@ def _solve_judgement(self, belief_task: Task, concept: Concept): 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 it - belief_task.reward_budget_priority(question_task.achieving_level()) + # 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_task.term.sub_terms @@ -393,8 +396,10 @@ def _solve_goal(self, goal_task: Task, concept: Concept, task_link: TaskLink=Non if belief_task is None or belief_task == old_best: return tasks, None elif belief_task != old_best: - belief_task.reward_budget_priority(goal_task.achieving_level()) - + 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(goal_task.sentence, belief_task.sentence, True) diff --git a/pynars/Narsese/_py/Task.py b/pynars/Narsese/_py/Task.py index d54e5f89..4c16e70a 100644 --- a/pynars/Narsese/_py/Task.py +++ b/pynars/Narsese/_py/Task.py @@ -40,7 +40,7 @@ def achieving_level(self, previous_belief: Truth=None): else: raise f'Invalid type! {type(self.sentence)}' - def reward_budget_priority(self, reward: float): + 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]): From 9d87de2c177039ad71a1964bbc645d3f96759729 Mon Sep 17 00:00:00 2001 From: ccrock4t <15344554+ccrock4t@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:32:43 -0500 Subject: [PATCH 12/17] Add simple eventbuffer and generate temporal results for #95 --- pynars/NARS/Control/Reasoner.py | 11 ++- .../NARS/DataStructures/MC/InputBufferMC.py | 6 +- pynars/NARS/DataStructures/_py/Buffer.py | 72 ++++++++++++++++++- pynars/Narsese/_py/Sentence.py | 4 ++ 4 files changed, 86 insertions(+), 7 deletions(-) diff --git a/pynars/NARS/Control/Reasoner.py b/pynars/NARS/Control/Reasoner.py index fb6d1028..105ceb1d 100644 --- a/pynars/NARS/Control/Reasoner.py +++ b/pynars/NARS/Control/Reasoner.py @@ -9,7 +9,7 @@ from pynars.Narsese._py.Budget import Budget from pynars.Narsese._py.Statement import Statement from pynars.Narsese._py.Task import Belief -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 from pynars import Config from pynars.Config import Enable @@ -38,6 +38,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] = [ @@ -131,11 +132,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 task_in.is_event: + 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/MC/InputBufferMC.py b/pynars/NARS/DataStructures/MC/InputBufferMC.py index 44ebcd49..3b45140c 100644 --- a/pynars/NARS/DataStructures/MC/InputBufferMC.py +++ b/pynars/NARS/DataStructures/MC/InputBufferMC.py @@ -330,9 +330,9 @@ def prediction_generation(self): for i in range(self.present): if self.slots[i].candidate: # e.g., (E, +1) as the subject - subject = Compound.SequentialEvents(self.slots[i].candidate.term, Interval(abs(self.present - i))) - copula = Copula.PredictiveImplication # =/> - term = Statement(subject, copula, predicate) + subject = Compound.SequentialEvents(scopula = Copula.PredictiveImplication # =/> + term = Statement(subject, copula, predicate)elf.slots[i].candidate.term, Interval(abs(self.present - i))) + # truth, using truth-induction function (TODO, may subject to change) truth = Truth_induction(self.slots[i].candidate.truth, self.slots[self.present].candidate.truth) diff --git a/pynars/NARS/DataStructures/_py/Buffer.py b/pynars/NARS/DataStructures/_py/Buffer.py index fd657e7c..b65cdac3 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,68 @@ 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 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 event_task_to_insert.is_event \ + or event_task_to_insert.term.type != TermType.STATEMENT \ + or event_task_to_insert.term.is_higher_order: + print("ERROR! Only events with first-order statements can enter the EventBuffer.") + return + + if len(self.buffer) == 0: # if nothing in the buffer, just insert it + self.buffer.append(event_task_to_insert) + return + + newest_event = self.buffer[-1] + + 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) \ No newline at end of file diff --git a/pynars/Narsese/_py/Sentence.py b/pynars/Narsese/_py/Sentence.py index 979fc8cf..cae40ddf 100644 --- a/pynars/Narsese/_py/Sentence.py +++ b/pynars/Narsese/_py/Sentence.py @@ -163,6 +163,10 @@ def is_event(self) -> bool: def is_external_event(self) -> bool: # TODO: ??? return not self.is_eternal and self.stamp.is_external + @property + def is_higher_order(self) -> bool: + return self.term is Statement and self.term.is_higher_order + class Judgement(Sentence): def __init__(self, term: Term, stamp: Stamp = None, truth: Truth = None) -> None: From 77513fba30cbc1224ead0162ff656aa2f28280e1 Mon Sep 17 00:00:00 2001 From: ccrock4t <15344554+ccrock4t@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:35:25 -0500 Subject: [PATCH 13/17] Update Sentence.py --- pynars/Narsese/_py/Sentence.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pynars/Narsese/_py/Sentence.py b/pynars/Narsese/_py/Sentence.py index cae40ddf..979fc8cf 100644 --- a/pynars/Narsese/_py/Sentence.py +++ b/pynars/Narsese/_py/Sentence.py @@ -163,10 +163,6 @@ def is_event(self) -> bool: def is_external_event(self) -> bool: # TODO: ??? return not self.is_eternal and self.stamp.is_external - @property - def is_higher_order(self) -> bool: - return self.term is Statement and self.term.is_higher_order - class Judgement(Sentence): def __init__(self, term: Term, stamp: Stamp = None, truth: Truth = None) -> None: From aba1badddaa09280174f743f25f0d76c9236855f Mon Sep 17 00:00:00 2001 From: ccrock4t <15344554+ccrock4t@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:36:43 -0500 Subject: [PATCH 14/17] Update InputBufferMC.py --- pynars/NARS/DataStructures/MC/InputBufferMC.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pynars/NARS/DataStructures/MC/InputBufferMC.py b/pynars/NARS/DataStructures/MC/InputBufferMC.py index 3b45140c..44ebcd49 100644 --- a/pynars/NARS/DataStructures/MC/InputBufferMC.py +++ b/pynars/NARS/DataStructures/MC/InputBufferMC.py @@ -330,9 +330,9 @@ def prediction_generation(self): for i in range(self.present): if self.slots[i].candidate: # e.g., (E, +1) as the subject - subject = Compound.SequentialEvents(scopula = Copula.PredictiveImplication # =/> - term = Statement(subject, copula, predicate)elf.slots[i].candidate.term, Interval(abs(self.present - i))) - + subject = Compound.SequentialEvents(self.slots[i].candidate.term, Interval(abs(self.present - i))) + copula = Copula.PredictiveImplication # =/> + term = Statement(subject, copula, predicate) # truth, using truth-induction function (TODO, may subject to change) truth = Truth_induction(self.slots[i].candidate.truth, self.slots[self.present].candidate.truth) From 3352758f9435781f73b70e73fcab7df62a4b81ef Mon Sep 17 00:00:00 2001 From: ccrock4t <15344554+ccrock4t@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:51:54 -0500 Subject: [PATCH 15/17] Don't allow compound events in EventBuffer --- pynars/NARS/Control/Reasoner.py | 2 +- pynars/NARS/DataStructures/_py/Buffer.py | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pynars/NARS/Control/Reasoner.py b/pynars/NARS/Control/Reasoner.py index 105ceb1d..a64f500c 100644 --- a/pynars/NARS/Control/Reasoner.py +++ b/pynars/NARS/Control/Reasoner.py @@ -137,7 +137,7 @@ def observe(self, tasks_derived: List[Task]): task_in: Task = channel.take() if task_in is not None: self.overall_experience.put(task_in) - if task_in.is_event: + 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() diff --git a/pynars/NARS/DataStructures/_py/Buffer.py b/pynars/NARS/DataStructures/_py/Buffer.py index b65cdac3..ebb5499f 100644 --- a/pynars/NARS/DataStructures/_py/Buffer.py +++ b/pynars/NARS/DataStructures/_py/Buffer.py @@ -80,9 +80,7 @@ def generate_temporal_sentences(self): return results def put(self, event_task_to_insert: Task): - if not event_task_to_insert.is_event \ - or event_task_to_insert.term.type != TermType.STATEMENT \ - or event_task_to_insert.term.is_higher_order: + if not self.can_task_enter(event_task_to_insert): print("ERROR! Only events with first-order statements can enter the EventBuffer.") return @@ -107,4 +105,9 @@ def put(self, event_task_to_insert: Task): if len(self.buffer) > self.capacity: # if too many events, take out the oldest event - self.buffer.pop(0) \ No newline at end of file + 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 From 19b10d85a52230025400b691eac120e994267f5b Mon Sep 17 00:00:00 2001 From: ccrock4t <15344554+ccrock4t@users.noreply.github.com> Date: Sat, 9 Mar 2024 21:51:47 -0500 Subject: [PATCH 16/17] Add EventBuffer Tests --- Tests/Test_Buffer.py | 133 +++++++++++++++++++++++ pynars/NARS/DataStructures/_py/Buffer.py | 14 ++- 2 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 Tests/Test_Buffer.py diff --git a/Tests/Test_Buffer.py b/Tests/Test_Buffer.py new file mode 100644 index 00000000..8269206d --- /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,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/NARS/DataStructures/_py/Buffer.py b/pynars/NARS/DataStructures/_py/Buffer.py index ebb5499f..b224ac88 100644 --- a/pynars/NARS/DataStructures/_py/Buffer.py +++ b/pynars/NARS/DataStructures/_py/Buffer.py @@ -61,6 +61,15 @@ 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 @@ -81,14 +90,13 @@ def generate_temporal_sentences(self): def put(self, event_task_to_insert: Task): if not self.can_task_enter(event_task_to_insert): - print("ERROR! Only events with first-order statements can enter the EventBuffer.") - return + 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.buffer[-1] + 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 From 02cf65609bb1352aee571dcff6e1490b4726ee0e Mon Sep 17 00:00:00 2001 From: ccrock4t <15344554+ccrock4t@users.noreply.github.com> Date: Sat, 9 Mar 2024 21:58:37 -0500 Subject: [PATCH 17/17] Fix spelling error in comment --- Tests/Test_Buffer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Test_Buffer.py b/Tests/Test_Buffer.py index 8269206d..50d27889 100644 --- a/Tests/Test_Buffer.py +++ b/Tests/Test_Buffer.py @@ -15,7 +15,7 @@ class TEST_EventBuffer(unittest.TestCase): def test_3_firstorder_event_temporal_chaining(self): """ - Add 3 first order events to the buffer (A-->B,B,C), each with different timestamps (A=1, B=2, C=3) + 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)