diff --git a/.vscode/launch.json b/.vscode/launch.json index 1899ec0..05eae4b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,10 +2,10 @@ "version": "0.2.0", "configurations": [ { - "name": "Python: Module", + "name": "Python: pynars.GUI", "type": "python", "request": "launch", - "module": "enter-your-module-name", + "module": "pynars.GUI", "justMyCode": true }, { @@ -268,7 +268,7 @@ "module": "Narsese.Parser._test" }, { - "name": "Python: 当前文件", + "name": "Python: Current File", "type": "python", "request": "launch", "program": "${file}", diff --git a/pynars/GUI/Backend.py b/pynars/GUI/Backend.py new file mode 100644 index 0000000..b20f81e --- /dev/null +++ b/pynars/GUI/Backend.py @@ -0,0 +1,113 @@ +from copy import deepcopy +from typing import Tuple, Union +from pathlib import Path +from multiprocessing import Process +import random +from pynars.NARS import Reasoner as Reasoner +from pynars.utils.Print import print_out, PrintType +from pynars.Narsese import Task +from typing import List +from pynars.utils.tools import rand_seed +import asyncio +from as_rpc import AioRpcServer, AioRpcClient, rpc +from functools import partial + + + +def run_line(nars: Reasoner, line: str): + '''''' + + ret = [] + satisfaction = [] + busyness = [] + alertness = [] + wellness = [] + + def handle_line(tasks_line: Tuple[List[Task], Task, Task, List[Task], Task, Tuple[Task, Task]]): + tasks_derived, judgement_revised, goal_revised, answers_question, answers_quest, ( + task_operation_return, task_executed) = tasks_line + + for task in tasks_derived: + ret.append(repr(task)) + + if judgement_revised is not None: + ret.append(repr(judgement_revised)) + if goal_revised is not None: + ret.append(repr(goal_revised)) + if answers_question is not None: + for answer in answers_question: + ret.append(repr(answer)) + if answers_quest is not None: + for answer in answers_quest: + ret.append(repr(answer)) + if task_executed is not None: + ret.append(f'{task_executed.term.repr()} = {str(task_operation_return) if task_operation_return is not None else None}') + + line = line.strip(' \n') + if line.startswith("'"): + return None + elif line.isdigit(): + n_cycle = int(line) + for _ in range(n_cycle): + tasks_all = nars.cycle() + satisfaction.append(nars.global_eval.S) + busyness.append(nars.global_eval.B) + alertness.append(nars.global_eval.A) + wellness.append(nars.global_eval.W) + handle_line(tasks_all) + else: + line = line.rstrip(' \n') + if len(line) == 0: + return ret + try: + success, task, _ = nars.input_narsese(line, go_cycle=False) + if success: + ret.append(repr(task)) + else: + ret.append(f':Invalid input! Failed to parse: {line}') + + tasks_all = nars.cycle() + satisfaction.append(nars.global_eval.S) + busyness.append(nars.global_eval.B) + alertness.append(nars.global_eval.A) + wellness.append(nars.global_eval.W) + handle_line(tasks_all) + except Exception as e: + ret.append(f':Unknown error: {line}. \n{e}') + return ret, (satisfaction, busyness, alertness, wellness) + + +def handle_lines(nars: Reasoner, lines: str): + ret = [] + satisfaction = [] + busyness = [] + alertness = [] + wellness = [] + for line in lines.split('\n'): + if len(line) == 0: + continue + + ret_line, (satisfaction_line, busynesse_line, alertness_line, wellbeing_line) = run_line(nars, line) + ret.extend(ret_line) + satisfaction.extend(satisfaction_line) + busyness.extend(busynesse_line) + alertness.extend(alertness_line) + wellness.extend(wellbeing_line) + return '\n'.join(ret), (satisfaction, busyness, alertness, wellness) + + +def run_nars(capacity_mem=1000, capacity_buff=1000): + seed = 137 + rand_seed(seed) + nars = Reasoner(capacity_mem, capacity_buff) + print_out(PrintType.COMMENT, 'Running with GUI.', comment_title='NARS') + server = AioRpcServer(buff=6553500) + rpc(server, "run_line")(run_line) + + def _handle_lines(content): + return handle_lines(nars, content) + rpc(server, "handle_lines")(_handle_lines) + server.init() + loop = asyncio.get_event_loop() + # loop.run_until_complete(run_nars()) + loop.run_forever() diff --git a/pynars/GUI/MainWindow.py b/pynars/GUI/MainWindow.py new file mode 100644 index 0000000..c0f7b17 --- /dev/null +++ b/pynars/GUI/MainWindow.py @@ -0,0 +1,238 @@ +import sys +from PySide6 import QtWidgets +# from PySide6 +from PySide6.QtWidgets import QMainWindow, QLabel, QPushButton, QApplication +from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QStackedLayout +from PySide6.QtWidgets import QFrame, QTextEdit, QToolBar, QPushButton, QSlider, QSplitter, QDockWidget +from PySide6.QtCore import Qt, QSize +from PySide6.QtGui import QScreen, QAction, QIcon, QColor, QFont, QKeyEvent, QFontDatabase +import qtawesome as qta +from .utils import change_stylesheet +from .Widgets.Button import Button +from .Widgets.Slider import Slider +from .Widgets.Plot import Plot +from as_rpc import AioRpcClient + +# create the application and the main window + + +class NARSWindow(QMainWindow): + client: AioRpcClient = None + + def __init__(self): + super().__init__() + self.init_layout() + + def init_layout(self): + ''' + initialize the layout + ''' + self.setGeometry(0, 0, 1000, 618) + self._center_window() + self.setWindowTitle('Open-NARS v4.0.0 (PyNARS)') + + central_widget = QWidget(self) + self.setCentralWidget(central_widget) + + # create left area + left_widget = QFrame(self) + left_widget.setFixedWidth(200) + left_widget.setContentsMargins(0, 0, 0, 0) + self.left_layout = QVBoxLayout(left_widget) # vertical layout + self.left_layout.setContentsMargins(0, 0, 0, 0) + self.left_layout.setSpacing(0) + + # create right-top and right-bottom areas + right_top_widget = QFrame(self) + right_top_widget.setContentsMargins(0, 0, 0, 0) + self.right_top_layout = QVBoxLayout( + right_top_widget) # vertical layout + self.right_top_layout.setContentsMargins(0, 0, 0, 0) + self.right_top_layout.setSpacing(0) + + right_bottom_widget = QFrame(self) + right_bottom_widget.setContentsMargins(0, 0, 0, 0) + self.right_bottom_layout = QVBoxLayout( + right_bottom_widget) # vertical layout + self.right_bottom_layout.setContentsMargins(0, 0, 0, 0) + self.right_bottom_layout.setSpacing(0) + + # create a splitter to adjust the right two areas + right_splitter = QSplitter(self) + right_splitter.setOrientation(Qt.Vertical) # vertical layout + + # add the widgets into the splitter + right_splitter.addWidget(right_top_widget) + right_splitter.addWidget(right_bottom_widget) + right_splitter.setSizes([500, 120]) + + # create main layout, and add the left widget and the right splitter into it + main_layout = QHBoxLayout(central_widget) # 水平布局 + main_layout.addWidget(left_widget) + main_layout.addWidget(right_splitter) # 添加可调整大小的右侧区域 + + self.init_output_toolbar() + self.init_output_textbox() + self.init_input_textbox() + + self.left_layout.addStretch(1) + self.init_plot() + + self.slider_fontsize.value_changed_connect( + lambda value: (change_stylesheet(self.text_output, f"font-size: {value+6}px;"), change_stylesheet(self.text_input, f"font-size: {value+6}px;"))) + + self.button_clear.clicked.connect( + lambda *args: self.text_output.clear()) + + def init_output_toolbar(self): + '''''' + # tools bar + toolbar = QWidget(self) + toolbar.setFixedHeight(35) + toolbar.setContentsMargins(0, 0, 0, 0) + toolbar_layout = QHBoxLayout(toolbar) + toolbar_layout.setContentsMargins(3, 0, 3, 0) + + self.right_top_layout.addWidget(toolbar) + + # create buttons + # run "qta-browser" to browse all the icons + icon_clear = qta.icon('ph.file', color=QColor('white')) + button_clear = Button(icon_clear) + toolbar_layout.addWidget(button_clear) + self.button_clear = button_clear + + icon_save = qta.icon('ph.floppy-disk', color=QColor('white')) + button_save = Button(icon_save) + toolbar_layout.addWidget(button_save) + self.button_save = button_save + + slider_fontsize = Slider(Qt.Horizontal, 6, 40, 12, " Font size: ") + slider_fontsize.setFixedWidth(100) + toolbar_layout.addWidget(slider_fontsize) + + # set stretch and spacing + toolbar_layout.addStretch(1) + toolbar_layout.setSpacing(3) + + self.slider_fontsize = slider_fontsize + + def init_output_textbox(self): + '''''' + # textbox of output + text_output = QTextEdit(self) + self.right_top_layout.addWidget(text_output) + text_output.setReadOnly(True) + # text = '\n'.join([f"This is line {i}" for i in range(50)]) + # text_output.setPlainText(text) + text_output.setStyleSheet( + "font-family: Consolas, Monaco, Courier New; color: white;") + + def output_clicked(text_output: QTextEdit): + print("line:", text_output.textCursor().blockNumber()) + text_output.mouseDoubleClickEvent = lambda x: output_clicked( + text_output) + self.text_output = text_output + + def init_input_textbox(self): + '''''' + text_input = QTextEdit(self) + self.right_bottom_layout.addWidget(text_input) + text_input.setReadOnly(False) + text_input.setStyleSheet( + "font-family: Consolas, Monaco, Courier New; color: white;") + text_input.installEventFilter(self) + self.text_input = text_input + + def init_plot(self): + ''' + Plots for global evaluations + ''' + dock = QDockWidget() + widget = QWidget() + widget.setContentsMargins(0,0,0,0) + layout = QVBoxLayout() + layout.setContentsMargins(0,0,0,0) + layout.setSpacing(0) + widget.setLayout(layout) + dock.setWidget(widget) + + self.plot_satisfaction = Plot() + self.plot_satisfaction.setTitle("satisfaction") + self.plot_satisfaction.setYRange(0.0, 1.0) + self.plot_satisfaction.setFixedHeight(100) + layout.addWidget(self.plot_satisfaction) + + self.plot_busyness = Plot() + self.plot_busyness.setTitle("busyness") + self.plot_busyness.setYRange(0.0, 1.0) + self.plot_busyness.setFixedHeight(100) + layout.addWidget(self.plot_busyness) + + self.plot_alertness = Plot() + self.plot_alertness.setTitle("alertness") + self.plot_alertness.setYRange(0.0, 1.0) + self.plot_alertness.setFixedHeight(100) + layout.addWidget(self.plot_alertness) + + self.plot_wellbeing = Plot() + self.plot_wellbeing.setTitle("well-being") + self.plot_wellbeing.setYRange(0.0, 1.0) + self.plot_wellbeing.setFixedHeight(100) + layout.addWidget(self.plot_wellbeing) + + self.left_layout.addWidget(dock) + + + def eventFilter(self, obj, event): + ''' + For the input textbox, when pressing Enter, the content should be input to NARS reasoner; but when pressing shift+Enter, just start a new line. + ''' + if obj == self.text_input and event.type() == QKeyEvent.KeyPress: + if event.key() == Qt.Key_Return: + if event.modifiers() == Qt.ShiftModifier: # pressing Shift+Enter + return super().eventFilter(obj, event) + else: # pressing Enter + self.input_narsese() + return True + return super().eventFilter(obj, event) + + def set_client(self, client: AioRpcClient): + self.client = client + + def input_narsese(self): + content: str = self.text_input.toPlainText() + self.text_input.clear() + if self.client is not None: + self.client.call_func("handle_lines", self.print_out, content) + else: + print(content) + + def print_out(self, content): + '''''' + # print(content) + out, err = content + if err is not None: + print(err) + self.text_output.append(':Error') + else: + text, (satisfaction, busyness, alertness, wellbeing) = out + # print(satisfactions) + if len(satisfaction) > 0: + self.plot_satisfaction.update_values(satisfaction) + self.plot_busyness.update_values(busyness) + self.plot_alertness.update_values(alertness) + self.plot_wellbeing.update_values(wellbeing) + if len(text) > 0: + self.text_output.append(text) + # self.text_output.append('\n') + + def _center_window(self): + ''' + Move the window to the center of the screen + ''' + center = QScreen.availableGeometry( + QApplication.primaryScreen()).center() + geo = self.frameGeometry() + geo.moveCenter(center) + self.move(geo.topLeft()) diff --git a/pynars/GUI/Widgets/Button.py b/pynars/GUI/Widgets/Button.py new file mode 100644 index 0000000..9547db4 --- /dev/null +++ b/pynars/GUI/Widgets/Button.py @@ -0,0 +1,9 @@ +from PySide6.QtWidgets import QPushButton + + +class Button(QPushButton): + def __init__(self, icon, text="", parent=None): + super().__init__(icon, text, parent) + self.setFixedSize(30, 30) + self.setIcon(icon) + self.setFlat(True) \ No newline at end of file diff --git a/pynars/GUI/Widgets/Plot.py b/pynars/GUI/Widgets/Plot.py new file mode 100644 index 0000000..734cccc --- /dev/null +++ b/pynars/GUI/Widgets/Plot.py @@ -0,0 +1,33 @@ +from PySide6 import QtWidgets, QtCore +from PySide6.QtWidgets import QWidget, QVBoxLayout +from pyqtgraph import PlotWidget, plot +import pyqtgraph as pg +import sys # We need sys so that we can pass argv to QApplication +import os +from random import randint +from pyqtgraph.widgets.PlotWidget import PlotWidget +import numpy as np + +class Plot(pg.PlotWidget): + + def __init__(self, parent=None): + super().__init__(parent) + + self.y = np.full(100, np.nan) # 100 time points + self.x = np.full(100, np.nan) # 100 data points + + pen = pg.mkPen(color=(0, 255, 0)) + self.data_line = self.plot(self.y, self.x, pen=pen) + + def update_values(self, values: 'float|list'): + if isinstance(values, float): values = (values,) + y0 = int(self.x[-1]) if not np.isnan(self.x[-1]) else 0 + if len(values) >= len(self.y): + self.y[:] = values[-len(self.y):] + self.x[:] = list(range(y0+len(values)-len(self.y)+1, y0+len(values)+1)) + else: + self.y[:-len(values)] = self.y[len(values):] + self.y[-len(values):] = values + self.x[:-len(values)] = self.x[len(values):] + self.x[-len(values):] = list(range(y0+1, y0+len(values)+1)) + self.data_line.setData(self.x, self.y) # Update the data. diff --git a/pynars/GUI/Widgets/Slider.py b/pynars/GUI/Widgets/Slider.py new file mode 100644 index 0000000..6a6d351 --- /dev/null +++ b/pynars/GUI/Widgets/Slider.py @@ -0,0 +1,46 @@ +from PySide6.QtWidgets import QSlider, QLabel +from PySide6.QtCore import Qt +from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QStackedLayout + +class Slider(QSlider): + def __init__(self, orientation=Qt.Horizontal, min=0, max=100, default=50, text="", parent=None): + super().__init__(orientation, parent) + self.setRange(min, max) + self.setSingleStep(1) + self.setValue(default) + self.setStyleSheet(""" + QSlider::groove:horizontal { + height: 30px; + background: rgba(255, 255, 255, 0); + } + QSlider::sub-page:horizontal { + height: 30px; + background: rgba(255, 255, 255, 0.3); + } + QSlider::add-page:horizontal { + height: 30px; + background: rgba(0, 0, 0, 0.5); + } + QSlider::handle:horizontal { + width: 0px; + height: 0px; + } + """) + layout = QStackedLayout(self) + self.setLayout(layout) + self.text = text + self.label = QLabel(self.text) + self.label.setAlignment(Qt.AlignVCenter) + layout.addWidget(self.label) + self._on_value_chagned(default) + self.valueChanged.connect(self._on_value_chagned) + + def _on_value_chagned(self, value): + self.label.setText(f"{self.text}{value}") + + def value_changed_connect(self, func): + def _func(value): + self._on_value_chagned(value) + func(value) + self.valueChanged.connect(_func) + _func(self.value()) diff --git a/pynars/GUI/__init__.py b/pynars/GUI/__init__.py new file mode 100644 index 0000000..bf2430b --- /dev/null +++ b/pynars/GUI/__init__.py @@ -0,0 +1,4 @@ +import sys +from PySide6 import QtWidgets +from .MainWindow import NARSWindow + diff --git a/pynars/GUI/__main__.py b/pynars/GUI/__main__.py new file mode 100644 index 0000000..59ae7da --- /dev/null +++ b/pynars/GUI/__main__.py @@ -0,0 +1,52 @@ +import sys +from PySide6 import QtWidgets +from qt_material import apply_stylesheet +import qdarkstyle +from .MainWindow import NARSWindow +from .Backend import run_nars +from multiprocessing import Process +from qasync import QEventLoop +import asyncio +from time import sleep, time +from as_rpc import AioRpcClient, rpc + +app = QtWidgets.QApplication(sys.argv) +loop = QEventLoop(app) + +asyncio.set_event_loop(loop) +# setup stylesheet +# apply_stylesheet(app, theme='dark_teal.xml') +app.setStyleSheet(qdarkstyle.load_stylesheet()) + + + +window = NARSWindow() +p_nars = Process(target=run_nars, args=(1000, 1000)) +p_nars.start() +# p_nars.join() + + +client = AioRpcClient(buff=6553500, mark='GUI') +timeout = 10 +t_begin = time() +while True: + try: + client.init() + break + except (ConnectionRefusedError, FileNotFoundError): + t_now = time() + if t_now - t_begin <= timeout: + sleep(1) + else: + raise TimeoutError("Cannot initialize NARS reasoner properly.") + +window.set_client(client) + +@rpc(client, "print_out") +def print_out(content): + window.print_out(content) + +# run +window.show() +with loop: + loop.run_forever() \ No newline at end of file diff --git a/pynars/GUI/utils.py b/pynars/GUI/utils.py new file mode 100644 index 0000000..70434fb --- /dev/null +++ b/pynars/GUI/utils.py @@ -0,0 +1,12 @@ + +from PySide6.QtWidgets import QWidget + +def change_stylesheet(widget: QWidget, stylesheet: str): + old_ss = widget.styleSheet() + old_ss = {(pair:=ss.split(':'))[0].strip(' '):pair[1].strip(' ') for ss in old_ss.strip(' ').split(';') if len(ss)>0} + new_ss = stylesheet + new_ss = {(pair:=ss.split(':'))[0].strip(' '):pair[1].strip(' ') for ss in new_ss.strip(' ').split(';') if len(ss)>0} + old_ss.update(new_ss) + new_ss = ''.join([f"{key}: {val}; " for key, val in old_ss.items()]) + widget.setStyleSheet(new_ss) + diff --git a/pynars/NARS/Control/Reasoner.py b/pynars/NARS/Control/Reasoner.py index 582cdc4..fb6d102 100644 --- a/pynars/NARS/Control/Reasoner.py +++ b/pynars/NARS/Control/Reasoner.py @@ -18,6 +18,8 @@ from pynars import Global from time import time from pynars.NAL.Functions.Tools import project_truth, project +from ..GlobalEval import GlobalEval + class Reasoner: @@ -26,12 +28,14 @@ def __init__(self, n_memory, capacity, config='./config.json', nal_rules={1, 2, # print('''Init...''') Config.load(config) + self.global_eval = GlobalEval() + self.inference = GeneralEngine(add_rules=nal_rules) self.variable_inference = VariableEngine(add_rules=nal_rules) self.temporal_inference = TemporalEngine( add_rules=nal_rules) # for temporal causal reasoning - self.memory = Memory(n_memory) + self.memory = Memory(n_memory, global_eval=self.global_eval) self.overall_experience = Buffer(capacity) self.internal_experience = Buffer(capacity) self.narsese_channel = NarseseChannel(capacity) @@ -177,7 +181,24 @@ def observe(self, tasks_derived: List[Task]): if answers_quest is not None: for answer in answers_quest: self.internal_experience.put(answer) - + # update busyness + self.global_eval.update_busyness(task.budget.priority) + + """ update alertness + Note: + according to [Wang, P., Talanov, M., & Hammer, P. (2016). The emotional mechanisms in NARS. In Artificial General Intelligence: 9th International Conference, AGI 2016, New York, NY, USA, July 16-19, 2016, Proceedings 9 (pp. 150-159). Springer International Publishing.](https://cis.temple.edu/~pwang/Publication/emotion.pdf) + > summarizes the average difference between recently processed input and the corresponding anticipations, so as to roughly indicate the extent to which the current environment is familiar. + The current code hasn't implemented `EventBuffer` yet. + The intuitive meaning of `alertness` is + > the extent to which the system’s knowledge is insufficient + (see [The Conceptual Design of OpenNARS 3.1.0](https://cis.temple.edu/tagit/publications/PAGI-TR-11.pdf)) + We tentatively exploit the truth of a revised task to indicate alertness + """ + if judgement_revised is not None: + self.global_eval.update_alertness( + judgement_revised.truth.c - task.truth.c) + else: + self.global_eval.update_alertness(0.0) # TODO: handling temporal induction and mental operation # Is it implemented correctly? @@ -238,6 +259,9 @@ def mental_operation(self, task: Task, concept: Concept, answers_question: Task, task_operation_return, task_executed = Operation.execute( task, concept, self.memory) + # update well-being + self.global_eval.update_wellbeing(task_executed.truth.e) + return task_operation_return, task_executed, belief_awared def register_operator(self, name_operator: str, callback: Callable): diff --git a/pynars/NARS/DataStructures/_py/Memory.py b/pynars/NARS/DataStructures/_py/Memory.py index fad8e22..32d87b1 100644 --- a/pynars/NARS/DataStructures/_py/Memory.py +++ b/pynars/NARS/DataStructures/_py/Memory.py @@ -18,12 +18,14 @@ 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 class Memory: - def __init__(self, capacity: int, n_buckets: int = None, take_in_order: bool = False, output_buffer = None) -> None: + def __init__(self, capacity: int, n_buckets: int = None, take_in_order: bool = False, output_buffer = None, global_eval: GlobalEval=None) -> None: # key: Callable[[Task], Any] = lambda task: task.term self.concepts = Bag(capacity, n_buckets=n_buckets, take_in_order=take_in_order) - self.output_buffer = output_buffer + # self.output_buffer = output_buffer + self.global_eval = global_eval if global_eval is not None else GlobalEval() @property def busyness(self): @@ -51,31 +53,31 @@ def accept(self, task: Task): answers_question, answer_quest = answers[Question], answers[Goal] elif task.is_goal: task_revised, belief_selected, (task_operation_return, task_executed), tasks_derived = self._accept_goal(task, concept) - # TODO, ugly! - if self.output_buffer is not None: - exist = False - for i in range(len(self.output_buffer.active_goals)): - if self.output_buffer.active_goals[i][0].term.equal(task.term): - self.output_buffer.active_goals = self.output_buffer.active_goals[:i] + [ - [task, "updated"]] + self.output_buffer.active_goals[i:] - exist = True - break - if not exist: - self.output_buffer.active_goals.append([task, "initialized"]) + # # TODO, ugly! + # if self.output_buffer is not None: + # exist = False + # for i in range(len(self.output_buffer.active_goals)): + # if self.output_buffer.active_goals[i][0].term.equal(task.term): + # self.output_buffer.active_goals = self.output_buffer.active_goals[:i] + [ + # [task, "updated"]] + self.output_buffer.active_goals[i:] + # exist = True + # break + # if not exist: + # self.output_buffer.active_goals.append([task, "initialized"]) elif task.is_question: # add the question to the question-table of the concept, and try to find a solution. answers_question = self._accept_question(task, concept) # TODO, ugly! - if self.output_buffer is not None: - exist = False - for i in range(len(self.output_buffer.active_questions)): - if self.output_buffer.active_questions[i][0].term.equal(task.term): - self.output_buffer.active_questions = self.output_buffer.active_questions[:i] + [ - [task, "updated"]] + self.output_buffer.active_questions[i:] - exist = True - break - if not exist: - self.output_buffer.active_questions.append([task, "initialized"]) + # if self.output_buffer is not None: + # exist = False + # for i in range(len(self.output_buffer.active_questions)): + # if self.output_buffer.active_questions[i][0].term.equal(task.term): + # self.output_buffer.active_questions = self.output_buffer.active_questions[:i] + [ + # [task, "updated"]] + self.output_buffer.active_questions[i:] + # exist = True + # break + # if not exist: + # self.output_buffer.active_questions.append([task, "initialized"]) elif task.is_quest: answer_quest = self._accept_quest(task, concept) else: @@ -259,6 +261,10 @@ def _accept_goal(self, task: Task, concept: Concept, task_link: TaskLink=None): if op in registered_operators and not task.is_mental_operation: # to judge whether the goal has been fulfilled task_operation_return, task_executed = execute(task, concept, self) + + # update well-being + self.global_eval.update_wellbeing(task_executed.truth.e) + concept_task = self.take_by_key(task.term, remove=False) if concept_task is not None: belief: Belief = concept_task.match_belief(task.sentence) @@ -269,6 +275,8 @@ def _accept_goal(self, task: Task, concept: Concept, task_link: TaskLink=None): # if task_operation_return is not None: tasks_derived.append(task_operation_return) # if task_executed is not None: tasks_derived.append(task_executed) + + @@ -370,6 +378,10 @@ def _solve_goal(self, task: Task, concept: Concept, task_link: TaskLink=None, be 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) + return tasks, None old_best = task.best_solution belief = belief or concept.match_belief(task.sentence) @@ -388,6 +400,12 @@ def _solve_goal(self, task: Task, concept: Concept, task_link: TaskLink=None, be if budget.is_above_thresh: task.budget = budget tasks.append(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) + return tasks, belief def _solve_quest(self, task: Task, concept: Concept): diff --git a/pynars/NARS/GlobalEval.py b/pynars/NARS/GlobalEval.py new file mode 100644 index 0000000..68cf734 --- /dev/null +++ b/pynars/NARS/GlobalEval.py @@ -0,0 +1,39 @@ +""" +This file implements the four global evaluations mentioned in [the design report of OpenNARS 3.1.0](https://cis.temple.edu/tagit/publications/PAGI-TR-11.pdf) + +- satisfaction: the extent to which the current situation meet the system’s desires, +- alertness: the extent to which the system’s knowledge is insufficient, +- busyness: the extent to which the system’s time resource is insufficient, +- well-being: the extent to which the system’s “body” functions as expected. +""" + + +class GlobalEval: + S: float = 0.5 # Satisfaction + A: float = 0.0 # Alertness + B: float = 0.5 # Busyness + W: float = 0.5 # Well-being + r: float = 0.05 # This is a global parameter + + def __init__(self) -> None: + pass + + def update_satisfaction(self, s, p): + '''''' + r = GlobalEval.r * p + self.S = r*s + (1-r)*self.S + + def update_alertness(self, a, p=1): + '''''' + r = GlobalEval.r * p + self.A = r*a + (1-r)*self.A + + def update_busyness(self, b, p=1): + '''''' + r = GlobalEval.r * p + self.B = r*b + (1-r)*self.B + + def update_wellbeing(self, w, p=1): + '''''' + r = GlobalEval.r * p + self.W = r*w + (1-r)*self.W diff --git a/requirements.txt b/requirements.txt index 204c0e5..7c93e88 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,4 +17,11 @@ tqdm<=3.1.4 typing>=3.7.4.3 typing_extensions>=4.0.1 sparse-lut>=1.0.0 -miniKanren>=1.0.3 \ No newline at end of file +miniKanren>=1.0.3 +# GUI related packages +qt-material>=2.14 +qtawesome>=1.2.3 +as-rpc>=1.0.1 +qasync +pyqtgraph +PySide6 \ No newline at end of file