From 1e7a1367ea7b326486c2bf37c8b1e312a46189ec Mon Sep 17 00:00:00 2001 From: Bowen Xu Date: Sat, 16 Sep 2023 15:38:12 -0400 Subject: [PATCH 01/16] Initialize NARSWindow. --- .vscode/launch.json | 6 +- pynars/GUI/MainWindow.py | 164 +++++++++++++++++++++++++++++++++++ pynars/GUI/Widgets/Button.py | 9 ++ pynars/GUI/Widgets/Slider.py | 46 ++++++++++ pynars/GUI/__init__.py | 4 + pynars/GUI/__main__.py | 18 ++++ pynars/GUI/utils.py | 12 +++ requirements.txt | 5 +- 8 files changed, 260 insertions(+), 4 deletions(-) create mode 100644 pynars/GUI/MainWindow.py create mode 100644 pynars/GUI/Widgets/Button.py create mode 100644 pynars/GUI/Widgets/Slider.py create mode 100644 pynars/GUI/__init__.py create mode 100644 pynars/GUI/__main__.py create mode 100644 pynars/GUI/utils.py diff --git a/.vscode/launch.json b/.vscode/launch.json index ec1f80ec..f3d83872 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 }, { @@ -247,7 +247,7 @@ "module": "Narsese.Parser._test" }, { - "name": "Python: 当前文件", + "name": "Python: Current File", "type": "python", "request": "launch", "program": "${file}", diff --git a/pynars/GUI/MainWindow.py b/pynars/GUI/MainWindow.py new file mode 100644 index 00000000..e2ea2d87 --- /dev/null +++ b/pynars/GUI/MainWindow.py @@ -0,0 +1,164 @@ +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 +from PySide6.QtCore import Qt, QSize +from PySide6.QtGui import QScreen, QAction, QIcon, QColor, QFont, QKeyEvent, QFontDatabase +from qt_material import apply_stylesheet +import qtawesome as qta +from .utils import change_stylesheet +from .Widgets.Button import Button +from .Widgets.Slider import Slider + +# create the application and the main window +class NARSWindow(QMainWindow): + 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 4.0.0 (PyNARS)') + + central_widget = QWidget(self) + self.setCentralWidget(central_widget) + + # create left area + left_widget = QFrame(self) + left_widget.setFixedWidth(200) + self.left_layout = QVBoxLayout(left_widget) # vertical layout + + # 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.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 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 input_narsese(self): + content: str = self.text_input.toPlainText() + self.text_input.clear() + print(content) + + 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 00000000..9547db48 --- /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/Slider.py b/pynars/GUI/Widgets/Slider.py new file mode 100644 index 00000000..6a6d351e --- /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 00000000..09412499 --- /dev/null +++ b/pynars/GUI/__init__.py @@ -0,0 +1,4 @@ +import sys +from PySide6 import QtWidgets +from .MainWindow import apply_stylesheet, NARSWindow + diff --git a/pynars/GUI/__main__.py b/pynars/GUI/__main__.py new file mode 100644 index 00000000..000b1df2 --- /dev/null +++ b/pynars/GUI/__main__.py @@ -0,0 +1,18 @@ +import sys +from PySide6 import QtWidgets +from .MainWindow import apply_stylesheet, NARSWindow +from .Console import run_nars +from multiprocessing import Process + +app = QtWidgets.QApplication(sys.argv) +# setup stylesheet +apply_stylesheet(app, theme='dark_teal.xml') + +window = NARSWindow() +p_nars = Process(target=run_nars, args=(None,)) +p_nars.start() +p_nars.join() + +# run +window.show() +app.exec() \ No newline at end of file diff --git a/pynars/GUI/utils.py b/pynars/GUI/utils.py new file mode 100644 index 00000000..70434fb0 --- /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/requirements.txt b/requirements.txt index 204c0e51..8c97e04c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,4 +17,7 @@ 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 \ No newline at end of file From b6f5350622205a7cf70c83a9897c97c1cd274b50 Mon Sep 17 00:00:00 2001 From: Bowen Xu Date: Sat, 16 Sep 2023 15:44:01 -0400 Subject: [PATCH 02/16] modify --- pynars/GUI/__main__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pynars/GUI/__main__.py b/pynars/GUI/__main__.py index 000b1df2..3e977afb 100644 --- a/pynars/GUI/__main__.py +++ b/pynars/GUI/__main__.py @@ -9,9 +9,9 @@ apply_stylesheet(app, theme='dark_teal.xml') window = NARSWindow() -p_nars = Process(target=run_nars, args=(None,)) -p_nars.start() -p_nars.join() +# p_nars = Process(target=run_nars, args=(None,)) +# p_nars.start() +# p_nars.join() # run window.show() From eb7e870a192c6eb2fd811b474d5f6b37a676c94f Mon Sep 17 00:00:00 2001 From: Bowen Xu Date: Sun, 17 Sep 2023 14:29:12 -0400 Subject: [PATCH 03/16] gui and reasnoner can communicate with each other now. --- .gitignore | 1 + pynars/GUI/Console.py | 94 ++++++++++++++++++++++++++++++++++++++++ pynars/GUI/MainWindow.py | 22 +++++++++- pynars/GUI/__main__.py | 42 ++++++++++++++++-- requirements.txt | 4 +- 5 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 pynars/GUI/Console.py diff --git a/.gitignore b/.gitignore index 5877f11f..fc2ae10e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ Tests/*.png !pynars/utils/SparseLUT/*.pyd lab.ipynb .DS_Store +cache # C extensions *.so diff --git a/pynars/GUI/Console.py b/pynars/GUI/Console.py new file mode 100644 index 00000000..ed9154cb --- /dev/null +++ b/pynars/GUI/Console.py @@ -0,0 +1,94 @@ +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 + +server = AioRpcServer(buff=6553500) + + +def run_line(nars: Reasoner, line: str): + '''''' + + ret = [] + + 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() + 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() + handle_line(tasks_all) + except Exception as e: + ret.append(f':Unknown error: {line}. \n{e}') + return ret + + +def handle_lines(nars: Reasoner, lines: str): + ret = [] + for line in lines.split('\n'): + if len(line) == 0: + continue + + ret_line = run_line(nars, line) + ret.extend(ret_line) + + return '\n'.join(ret) + + +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') + 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 index e2ea2d87..92e8b974 100644 --- a/pynars/GUI/MainWindow.py +++ b/pynars/GUI/MainWindow.py @@ -11,13 +11,16 @@ from .utils import change_stylesheet from .Widgets.Button import Button from .Widgets.Slider import Slider +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 @@ -145,10 +148,27 @@ def eventFilter(self, obj, event): 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() - print(content) + 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: + self.text_output.append(out) + # self.text_output.append('\n') def _center_window(self): ''' diff --git a/pynars/GUI/__main__.py b/pynars/GUI/__main__.py index 3e977afb..e3cbf105 100644 --- a/pynars/GUI/__main__.py +++ b/pynars/GUI/__main__.py @@ -3,16 +3,52 @@ from .MainWindow import apply_stylesheet, NARSWindow from .Console 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 = asyncio.new_event_loop() +# async def foo(): +# print('foo') +# loop.create_task(foo()) +# loop._ready +loop = QEventLoop(app) + +asyncio.set_event_loop(loop) # setup stylesheet apply_stylesheet(app, theme='dark_teal.xml') + + window = NARSWindow() -# p_nars = Process(target=run_nars, args=(None,)) -# p_nars.start() +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 as e: + 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() -app.exec() \ No newline at end of file +with loop: + loop.run_forever() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 8c97e04c..6b8bc208 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,4 +20,6 @@ sparse-lut>=1.0.0 miniKanren>=1.0.3 # GUI related packages qt-material>=2.14 -qtawesome>=1.2.3 \ No newline at end of file +qtawesome>=1.2.3 +as-prc>=1.0.1 +qasync \ No newline at end of file From 2afa600174d9ab13bad71a610fffa51a37e00ccd Mon Sep 17 00:00:00 2001 From: Bowen Xu Date: Sun, 17 Sep 2023 16:39:31 -0400 Subject: [PATCH 04/16] start point --- pynars/NARS/Control/GlobalEval.py | 39 +++++++++++++++++++++++++++++++ pynars/NARS/Control/Reasoner.py | 4 ++++ 2 files changed, 43 insertions(+) create mode 100644 pynars/NARS/Control/GlobalEval.py diff --git a/pynars/NARS/Control/GlobalEval.py b/pynars/NARS/Control/GlobalEval.py new file mode 100644 index 00000000..d315be2d --- /dev/null +++ b/pynars/NARS/Control/GlobalEval.py @@ -0,0 +1,39 @@ +""" +This file implement 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.5 # 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, w, p): + '''''' + r = GlobalEval.r * p + self.W = r*w + (1-r)*self.W + + def update_busyness(self, b, p): + '''''' + r = GlobalEval.r * p + self.B = r*b + (1-r)*self.B + + def update_wellbeing(self, w, p): + '''''' + r = GlobalEval.r * p + self.W = r*w + (1-r)*self.W diff --git a/pynars/NARS/Control/Reasoner.py b/pynars/NARS/Control/Reasoner.py index fdf43fc3..3dfe52ad 100644 --- a/pynars/NARS/Control/Reasoner.py +++ b/pynars/NARS/Control/Reasoner.py @@ -17,6 +17,8 @@ from pynars import Global from time import time from pynars.NAL.Functions.Tools import project_truth, project +from .GlobalEval import GlobalEval + class Reasoner: @@ -41,6 +43,8 @@ def __init__(self, n_memory, capacity, config = './config.json') -> None: self.sequence_buffer = Buffer(capacity) self.operations_buffer = Buffer(capacity) + self.global_eval = GlobalEval() + def reset(self): '''''' # TODO From 6c9aec660956e9c77d5b9369b1fd1a3e44cd5c71 Mon Sep 17 00:00:00 2001 From: Bowen Xu Date: Sun, 17 Sep 2023 19:44:27 -0400 Subject: [PATCH 05/16] fix bug --- pynars/GUI/__main__.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pynars/GUI/__main__.py b/pynars/GUI/__main__.py index e3cbf105..c9a89ca9 100644 --- a/pynars/GUI/__main__.py +++ b/pynars/GUI/__main__.py @@ -9,11 +9,6 @@ from as_rpc import AioRpcClient, rpc app = QtWidgets.QApplication(sys.argv) -# loop = asyncio.new_event_loop() -# async def foo(): -# print('foo') -# loop.create_task(foo()) -# loop._ready loop = QEventLoop(app) asyncio.set_event_loop(loop) @@ -35,7 +30,7 @@ try: client.init() break - except ConnectionRefusedError as e: + except (ConnectionRefusedError, FileNotFoundError): t_now = time() if t_now - t_begin <= timeout: sleep(1) From aacdb53fdd2073796e5acb8cc6e3024d490217fd Mon Sep 17 00:00:00 2001 From: Bowen Xu Date: Sun, 17 Sep 2023 22:08:12 -0400 Subject: [PATCH 06/16] can do evaluation of satisfaction now. --- pynars/GUI/Console.py | 13 ++++-- pynars/GUI/MainWindow.py | 58 ++++++++++++++++-------- pynars/GUI/Widgets/Plot.py | 33 ++++++++++++++ pynars/NARS/Control/Reasoner.py | 7 +-- pynars/NARS/DataStructures/_py/Memory.py | 35 ++++++++------ pynars/NARS/{Control => }/GlobalEval.py | 0 requirements.txt | 4 +- 7 files changed, 110 insertions(+), 40 deletions(-) create mode 100644 pynars/GUI/Widgets/Plot.py rename pynars/NARS/{Control => }/GlobalEval.py (100%) diff --git a/pynars/GUI/Console.py b/pynars/GUI/Console.py index ed9154cb..24aae13c 100644 --- a/pynars/GUI/Console.py +++ b/pynars/GUI/Console.py @@ -12,13 +12,13 @@ from as_rpc import AioRpcServer, AioRpcClient, rpc from functools import partial -server = AioRpcServer(buff=6553500) def run_line(nars: Reasoner, line: str): '''''' ret = [] + satisfactions = [] 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, ( @@ -47,6 +47,7 @@ def handle_line(tasks_line: Tuple[List[Task], Task, Task, List[Task], Task, Tupl n_cycle = int(line) for _ in range(n_cycle): tasks_all = nars.cycle() + satisfactions.append(nars.global_eval.S) handle_line(tasks_all) else: line = line.rstrip(' \n') @@ -60,22 +61,25 @@ def handle_line(tasks_line: Tuple[List[Task], Task, Task, List[Task], Task, Tupl ret.append(f':Invalid input! Failed to parse: {line}') tasks_all = nars.cycle() + satisfactions.append(nars.global_eval.S) handle_line(tasks_all) except Exception as e: ret.append(f':Unknown error: {line}. \n{e}') - return ret + return ret, satisfactions def handle_lines(nars: Reasoner, lines: str): ret = [] + satisfactions = [] for line in lines.split('\n'): if len(line) == 0: continue - ret_line = run_line(nars, line) + ret_line, satisfactions_line = run_line(nars, line) ret.extend(ret_line) + satisfactions.extend(satisfactions_line) - return '\n'.join(ret) + return '\n'.join(ret), satisfactions def run_nars(capacity_mem=1000, capacity_buff=1000): @@ -83,6 +87,7 @@ def run_nars(capacity_mem=1000, capacity_buff=1000): 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): diff --git a/pynars/GUI/MainWindow.py b/pynars/GUI/MainWindow.py index 92e8b974..4f9b34b7 100644 --- a/pynars/GUI/MainWindow.py +++ b/pynars/GUI/MainWindow.py @@ -11,23 +11,26 @@ 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 4.0.0 (PyNARS)') + self.setWindowTitle('Open-NARS v4.0.0 (PyNARS)') central_widget = QWidget(self) self.setCentralWidget(central_widget) @@ -35,7 +38,10 @@ def init_layout(self): # 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) @@ -44,7 +50,7 @@ def init_layout(self): 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( @@ -70,9 +76,12 @@ def init_layout(self): 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()) @@ -86,7 +95,7 @@ def init_output_toolbar(self): 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')) @@ -103,12 +112,11 @@ def init_output_toolbar(self): slider_fontsize.setFixedWidth(100) toolbar_layout.addWidget(slider_fontsize) - # set stretch and spacing + # set stretch and spacing toolbar_layout.addStretch(1) toolbar_layout.setSpacing(3) self.slider_fontsize = slider_fontsize - def init_output_textbox(self): '''''' @@ -118,8 +126,9 @@ def init_output_textbox(self): 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;") - + 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( @@ -131,23 +140,33 @@ 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.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 + ''' + self.plot_satisfaction = Plot() + self.plot_satisfaction.setYRange(0.0, 1.0) + self.plot_satisfaction.setFixedHeight(100) + self.left_layout.addWidget(self.plot_satisfaction) + 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 + if event.modifiers() == Qt.ShiftModifier: # pressing Shift+Enter return super().eventFilter(obj, event) - else: # pressing Enter + else: # pressing Enter self.input_narsese() return True return super().eventFilter(obj, event) - + def set_client(self, client: AioRpcClient): self.client = client @@ -158,7 +177,7 @@ def input_narsese(self): self.client.call_func("handle_lines", self.print_out, content) else: print(content) - + def print_out(self, content): '''''' # print(content) @@ -167,7 +186,11 @@ def print_out(self, content): print(err) self.text_output.append(':Error') else: - self.text_output.append(out) + text, satisfactions = out + # print(satisfactions) + if len(satisfactions) > 0: + self.plot_satisfaction.update_values(satisfactions) + self.text_output.append(text) # self.text_output.append('\n') def _center_window(self): @@ -179,6 +202,3 @@ def _center_window(self): geo = self.frameGeometry() geo.moveCenter(center) self.move(geo.topLeft()) - - - diff --git a/pynars/GUI/Widgets/Plot.py b/pynars/GUI/Widgets/Plot.py new file mode 100644 index 00000000..734cccc6 --- /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/NARS/Control/Reasoner.py b/pynars/NARS/Control/Reasoner.py index 3dfe52ad..cc839115 100644 --- a/pynars/NARS/Control/Reasoner.py +++ b/pynars/NARS/Control/Reasoner.py @@ -17,7 +17,7 @@ from pynars import Global from time import time from pynars.NAL.Functions.Tools import project_truth, project -from .GlobalEval import GlobalEval +from ..GlobalEval import GlobalEval class Reasoner: @@ -26,11 +26,13 @@ def __init__(self, n_memory, capacity, config = './config.json') -> None: # print('''Init...''') Config.load(config) + self.global_eval = GlobalEval() + self.inference = GeneralEngine() self.variable_inference = VariableEngine() self.temporal_inference = TemporalEngine() # 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) @@ -43,7 +45,6 @@ def __init__(self, n_memory, capacity, config = './config.json') -> None: self.sequence_buffer = Buffer(capacity) self.operations_buffer = Buffer(capacity) - self.global_eval = GlobalEval() def reset(self): '''''' diff --git a/pynars/NARS/DataStructures/_py/Memory.py b/pynars/NARS/DataStructures/_py/Memory.py index 1bc6fe85..023492be 100644 --- a/pynars/NARS/DataStructures/_py/Memory.py +++ b/pynars/NARS/DataStructures/_py/Memory.py @@ -16,12 +16,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() def accept(self, task: Task): ''' @@ -40,17 +42,17 @@ def accept(self, task: Task): task_revised, answers_question = self._accept_judgement(task, concept) 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) @@ -361,6 +363,7 @@ def _solve_goal(self, task: Task, concept: Concept, task_link: TaskLink=None, be 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 if old_best is not None: @@ -374,6 +377,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/Control/GlobalEval.py b/pynars/NARS/GlobalEval.py similarity index 100% rename from pynars/NARS/Control/GlobalEval.py rename to pynars/NARS/GlobalEval.py diff --git a/requirements.txt b/requirements.txt index 6b8bc208..986f9b1e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,4 +22,6 @@ miniKanren>=1.0.3 qt-material>=2.14 qtawesome>=1.2.3 as-prc>=1.0.1 -qasync \ No newline at end of file +qasync +pyqtgraph +PySide6 \ No newline at end of file From 89d6385f75da3e2618561d2339cc35097fd0fc9d Mon Sep 17 00:00:00 2001 From: Bowen Xu Date: Wed, 20 Sep 2023 13:40:44 -0400 Subject: [PATCH 07/16] modify --- pynars/NARS/DataStructures/_py/Memory.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pynars/NARS/DataStructures/_py/Memory.py b/pynars/NARS/DataStructures/_py/Memory.py index 023492be..34846ea0 100644 --- a/pynars/NARS/DataStructures/_py/Memory.py +++ b/pynars/NARS/DataStructures/_py/Memory.py @@ -57,16 +57,16 @@ def accept(self, task: Task): # 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: From 689ce837173060c258f48f74538538cfa90b69b8 Mon Sep 17 00:00:00 2001 From: Bowen Xu Date: Wed, 20 Sep 2023 15:32:17 -0400 Subject: [PATCH 08/16] avoid printing empty line --- pynars/GUI/MainWindow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pynars/GUI/MainWindow.py b/pynars/GUI/MainWindow.py index 92e8b974..56bc48f1 100644 --- a/pynars/GUI/MainWindow.py +++ b/pynars/GUI/MainWindow.py @@ -167,7 +167,8 @@ def print_out(self, content): print(err) self.text_output.append(':Error') else: - self.text_output.append(out) + if len(out) > 0: + self.text_output.append(out) # self.text_output.append('\n') def _center_window(self): From bcf0590ddcd92302d9f09d44ad4fdbcf46be8a10 Mon Sep 17 00:00:00 2001 From: Bowen Xu Date: Thu, 21 Sep 2023 16:45:33 -0400 Subject: [PATCH 09/16] add plots to gui --- pynars/GUI/MainWindow.py | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/pynars/GUI/MainWindow.py b/pynars/GUI/MainWindow.py index dbffeb18..a676a421 100644 --- a/pynars/GUI/MainWindow.py +++ b/pynars/GUI/MainWindow.py @@ -3,7 +3,7 @@ # 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 +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 from qt_material import apply_stylesheet @@ -149,10 +149,41 @@ 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) - self.left_layout.addWidget(self.plot_satisfaction) + 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): ''' From 6428fcf236b560c3411730433665f9676f72751b Mon Sep 17 00:00:00 2001 From: Bowen Xu Date: Thu, 21 Sep 2023 17:20:35 -0400 Subject: [PATCH 10/16] display busyness --- pynars/GUI/Console.py | 11 ++++++++--- pynars/GUI/MainWindow.py | 4 ++-- pynars/GUI/__init__.py | 2 +- pynars/GUI/__main__.py | 3 ++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pynars/GUI/Console.py b/pynars/GUI/Console.py index 24aae13c..843590d1 100644 --- a/pynars/GUI/Console.py +++ b/pynars/GUI/Console.py @@ -19,6 +19,7 @@ def run_line(nars: Reasoner, line: str): ret = [] satisfactions = [] + busynesses = [] 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, ( @@ -48,6 +49,7 @@ def handle_line(tasks_line: Tuple[List[Task], Task, Task, List[Task], Task, Tupl for _ in range(n_cycle): tasks_all = nars.cycle() satisfactions.append(nars.global_eval.S) + busynesses.append(nars.global_eval.B) handle_line(tasks_all) else: line = line.rstrip(' \n') @@ -62,24 +64,27 @@ def handle_line(tasks_line: Tuple[List[Task], Task, Task, List[Task], Task, Tupl tasks_all = nars.cycle() satisfactions.append(nars.global_eval.S) + busynesses.append(nars.global_eval.B) handle_line(tasks_all) except Exception as e: ret.append(f':Unknown error: {line}. \n{e}') - return ret, satisfactions + return ret, (satisfactions, busynesses) def handle_lines(nars: Reasoner, lines: str): ret = [] satisfactions = [] + busynesses = [] for line in lines.split('\n'): if len(line) == 0: continue - ret_line, satisfactions_line = run_line(nars, line) + ret_line, (satisfactions_line, busynesses_line) = run_line(nars, line) ret.extend(ret_line) satisfactions.extend(satisfactions_line) + busynesses.extend(busynesses_line) - return '\n'.join(ret), satisfactions + return '\n'.join(ret), (satisfactions, busynesses) def run_nars(capacity_mem=1000, capacity_buff=1000): diff --git a/pynars/GUI/MainWindow.py b/pynars/GUI/MainWindow.py index a676a421..e4ca8998 100644 --- a/pynars/GUI/MainWindow.py +++ b/pynars/GUI/MainWindow.py @@ -6,7 +6,6 @@ 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 -from qt_material import apply_stylesheet import qtawesome as qta from .utils import change_stylesheet from .Widgets.Button import Button @@ -217,10 +216,11 @@ def print_out(self, content): print(err) self.text_output.append(':Error') else: - text, satisfactions = out + text, (satisfactions, busynesses) = out # print(satisfactions) if len(satisfactions) > 0: self.plot_satisfaction.update_values(satisfactions) + self.plot_busyness.update_values(busynesses) if len(text) > 0: self.text_output.append(text) # self.text_output.append('\n') diff --git a/pynars/GUI/__init__.py b/pynars/GUI/__init__.py index 09412499..bf2430bb 100644 --- a/pynars/GUI/__init__.py +++ b/pynars/GUI/__init__.py @@ -1,4 +1,4 @@ import sys from PySide6 import QtWidgets -from .MainWindow import apply_stylesheet, NARSWindow +from .MainWindow import NARSWindow diff --git a/pynars/GUI/__main__.py b/pynars/GUI/__main__.py index c9a89ca9..709f6d52 100644 --- a/pynars/GUI/__main__.py +++ b/pynars/GUI/__main__.py @@ -1,6 +1,7 @@ import sys from PySide6 import QtWidgets -from .MainWindow import apply_stylesheet, NARSWindow +from qt_material import apply_stylesheet +from .MainWindow import NARSWindow from .Console import run_nars from multiprocessing import Process from qasync import QEventLoop From 46a25eab63b9be7668c22cc380be538ced40b4b1 Mon Sep 17 00:00:00 2001 From: Bowen Xu Date: Tue, 17 Oct 2023 13:16:32 -0400 Subject: [PATCH 11/16] modify --- pynars/GUI/{Console.py => Backend.py} | 0 pynars/GUI/__main__.py | 6 ++++-- 2 files changed, 4 insertions(+), 2 deletions(-) rename pynars/GUI/{Console.py => Backend.py} (100%) diff --git a/pynars/GUI/Console.py b/pynars/GUI/Backend.py similarity index 100% rename from pynars/GUI/Console.py rename to pynars/GUI/Backend.py diff --git a/pynars/GUI/__main__.py b/pynars/GUI/__main__.py index 709f6d52..59ae7da9 100644 --- a/pynars/GUI/__main__.py +++ b/pynars/GUI/__main__.py @@ -1,8 +1,9 @@ import sys from PySide6 import QtWidgets from qt_material import apply_stylesheet +import qdarkstyle from .MainWindow import NARSWindow -from .Console import run_nars +from .Backend import run_nars from multiprocessing import Process from qasync import QEventLoop import asyncio @@ -14,7 +15,8 @@ asyncio.set_event_loop(loop) # setup stylesheet -apply_stylesheet(app, theme='dark_teal.xml') +# apply_stylesheet(app, theme='dark_teal.xml') +app.setStyleSheet(qdarkstyle.load_stylesheet()) From 6c1c95192a619ce0fd6829fce76e5d723b6aa593 Mon Sep 17 00:00:00 2001 From: Bowen Xu Date: Wed, 8 Nov 2023 18:41:17 -0500 Subject: [PATCH 12/16] plot all the four evaluators --- pynars/GUI/Backend.py | 37 +++++++++++++++++++++++-------------- pynars/GUI/MainWindow.py | 10 ++++++---- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/pynars/GUI/Backend.py b/pynars/GUI/Backend.py index 843590d1..b20f81eb 100644 --- a/pynars/GUI/Backend.py +++ b/pynars/GUI/Backend.py @@ -18,8 +18,10 @@ def run_line(nars: Reasoner, line: str): '''''' ret = [] - satisfactions = [] - busynesses = [] + 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, ( @@ -48,8 +50,10 @@ def handle_line(tasks_line: Tuple[List[Task], Task, Task, List[Task], Task, Tupl n_cycle = int(line) for _ in range(n_cycle): tasks_all = nars.cycle() - satisfactions.append(nars.global_eval.S) - busynesses.append(nars.global_eval.B) + 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') @@ -63,28 +67,33 @@ def handle_line(tasks_line: Tuple[List[Task], Task, Task, List[Task], Task, Tupl ret.append(f':Invalid input! Failed to parse: {line}') tasks_all = nars.cycle() - satisfactions.append(nars.global_eval.S) - busynesses.append(nars.global_eval.B) + 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, (satisfactions, busynesses) + return ret, (satisfaction, busyness, alertness, wellness) def handle_lines(nars: Reasoner, lines: str): ret = [] - satisfactions = [] - busynesses = [] + satisfaction = [] + busyness = [] + alertness = [] + wellness = [] for line in lines.split('\n'): if len(line) == 0: continue - ret_line, (satisfactions_line, busynesses_line) = run_line(nars, line) + ret_line, (satisfaction_line, busynesse_line, alertness_line, wellbeing_line) = run_line(nars, line) ret.extend(ret_line) - satisfactions.extend(satisfactions_line) - busynesses.extend(busynesses_line) - - return '\n'.join(ret), (satisfactions, busynesses) + 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): diff --git a/pynars/GUI/MainWindow.py b/pynars/GUI/MainWindow.py index e4ca8998..c0f7b17a 100644 --- a/pynars/GUI/MainWindow.py +++ b/pynars/GUI/MainWindow.py @@ -216,11 +216,13 @@ def print_out(self, content): print(err) self.text_output.append(':Error') else: - text, (satisfactions, busynesses) = out + text, (satisfaction, busyness, alertness, wellbeing) = out # print(satisfactions) - if len(satisfactions) > 0: - self.plot_satisfaction.update_values(satisfactions) - self.plot_busyness.update_values(busynesses) + 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') From bd84a0b4f30919e0e59eeabaf8ac1d4b352c794a Mon Sep 17 00:00:00 2001 From: Bowen Xu Date: Wed, 8 Nov 2023 19:33:35 -0500 Subject: [PATCH 13/16] implemented --- pynars/NARS/Control/Reasoner.py | 64 +++++++++++++++++++++++---------- pynars/NARS/GlobalEval.py | 14 ++++---- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/pynars/NARS/Control/Reasoner.py b/pynars/NARS/Control/Reasoner.py index 677f6730..f5edcbb8 100644 --- a/pynars/NARS/Control/Reasoner.py +++ b/pynars/NARS/Control/Reasoner.py @@ -22,7 +22,7 @@ class Reasoner: - def __init__(self, n_memory, capacity, config = './config.json', nal_rules={1,2,3,4,5,6,7,8,9}) -> None: + def __init__(self, n_memory, capacity, config='./config.json', nal_rules={1, 2, 3, 4, 5, 6, 7, 8, 9}) -> None: # print('''Init...''') Config.load(config) @@ -30,7 +30,8 @@ def __init__(self, n_memory, capacity, config = './config.json', nal_rules={1,2, 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.temporal_inference = TemporalEngine( + add_rules=nal_rules) # for temporal causal reasoning self.memory = Memory(n_memory, global_eval=self.global_eval) self.overall_experience = Buffer(capacity) @@ -45,7 +46,6 @@ def __init__(self, n_memory, capacity, config = './config.json', nal_rules={1,2, self.sequence_buffer = Buffer(capacity) self.operations_buffer = Buffer(capacity) - def reset(self): '''''' # TODO @@ -58,7 +58,7 @@ def cycles(self, n_cycle: int): def input_narsese(self, text, go_cycle: bool = False) -> Tuple[bool, Union[Task, None], Union[Task, None]]: success, task, task_overflow = self.narsese_channel.put(text) - if go_cycle: + if go_cycle: tasks = self.cycle() return success, task, task_overflow, tasks return success, task, task_overflow @@ -87,12 +87,14 @@ def cycle(self): task.processed = True # if task.is_goal: # print(task) - + # concept = self.memory.take_by_key(task.term, remove=False) # if task.is_goal: - # goal_revised = self.process_goal(task, concept) - judgement_revised, goal_revised, answers_question, answers_quest, (task_operation_return, task_executed), _tasks_derived = self.memory.accept(task) - if task_operation_return is not None: tasks_derived.append(task_operation_return) + # goal_revised = self.process_goal(task, concept) + judgement_revised, goal_revised, answers_question, answers_quest, ( + task_operation_return, task_executed), _tasks_derived = self.memory.accept(task) + if task_operation_return is not None: + tasks_derived.append(task_operation_return) # if task_executed is not None: tasks_derived.append(task_executed) tasks_derived.extend(_tasks_derived) # self.sequence_buffer.put_back(task) # globalBuffer.putBack(task, @@ -116,8 +118,25 @@ def cycle(self): 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) else: judgement_revised, goal_revised, answers_question, answers_quest = None, None, None, None + """ 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) # step 4. Apply inference step # general inference step @@ -125,14 +144,15 @@ def cycle(self): if concept is not None: tasks_inference_derived = self.inference.step(concept) tasks_derived.extend(tasks_inference_derived) - - is_concept_valid = True # TODO + + is_concept_valid = True # TODO if is_concept_valid: self.memory.put_back(concept) # temporal induction in NAL-7 if Enable.temporal_reasoning and task is not None and task.is_judgement and task.is_external_event: - concept_task: Concept = self.memory.take_by_key(task.term, remove=False) + concept_task: Concept = self.memory.take_by_key( + task.term, remove=False) t1 = time() tasks_derived.extend( self.temporal_inference.step( @@ -147,12 +167,15 @@ def cycle(self): pass # TODO: select a task from `self.sequence_buffer`? # mental operation of NAL-9 - if Enable.operation: # it should be `Enable.mental_operation`? + if Enable.operation: # it should be `Enable.mental_operation`? task_operation_return, task_executed, belief_awared = self.mental_operation(task, concept, answers_question, answers_quest) - if task_operation_return is not None: tasks_derived.append(task_operation_return) - if task_executed is not None: tasks_derived.append(task_executed) - if belief_awared is not None: tasks_derived.append(belief_awared) + if task_operation_return is not None: + tasks_derived.append(task_operation_return) + if task_executed is not None: + tasks_derived.append(task_executed) + if belief_awared is not None: + tasks_derived.append(belief_awared) # put the derived tasks into the internal-experience. for task_derived in tasks_derived: @@ -161,7 +184,8 @@ def cycle(self): # handle the sense of time Global.time += 1 thresh_complexity = 20 - tasks_derived = [task for task in tasks_derived if task.term.complexity <= thresh_complexity] + tasks_derived = [ + task for task in tasks_derived if task.term.complexity <= thresh_complexity] return tasks_derived, judgement_revised, goal_revised, answers_question, answers_quest, ( task_operation_return, task_executed) @@ -171,7 +195,8 @@ def mental_operation(self, task: Task, concept: Concept, answers_question: Task, # belief-awareness for answers in (answers_question, answers_quest): - if answers is None: continue + if answers is None: + continue for answer in answers: belief_awared = Operation.aware__believe(answer) @@ -185,7 +210,9 @@ def mental_operation(self, task: Task, concept: Concept, answers_question: Task, # execute mental operation if task is not None and task.is_executable: - task_operation_return, task_executed = Operation.execute(task, concept, self.memory) + task_operation_return, task_executed = Operation.execute( + task, concept, self.memory) + self.global_eval.update_wellbeing(task_executed.truth.e) return task_operation_return, task_executed, belief_awared @@ -197,4 +224,3 @@ def register_operator(self, name_operator: str, callback: Callable): Operation.register(op, callback) return op return None - diff --git a/pynars/NARS/GlobalEval.py b/pynars/NARS/GlobalEval.py index d315be2d..68cf7341 100644 --- a/pynars/NARS/GlobalEval.py +++ b/pynars/NARS/GlobalEval.py @@ -1,5 +1,5 @@ """ -This file implement the four global evaluations mentioned in [the design report of OpenNARS 3.1.0](https://cis.temple.edu/tagit/publications/PAGI-TR-11.pdf) +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, @@ -9,8 +9,8 @@ class GlobalEval: - S: float = 0.5 # satisfaction - A: float = 0.5 # alertness + 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 @@ -23,17 +23,17 @@ def update_satisfaction(self, s, p): r = GlobalEval.r * p self.S = r*s + (1-r)*self.S - def update_alertness(self, w, p): + def update_alertness(self, a, p=1): '''''' r = GlobalEval.r * p - self.W = r*w + (1-r)*self.W + self.A = r*a + (1-r)*self.A - def update_busyness(self, b, p): + 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): + def update_wellbeing(self, w, p=1): '''''' r = GlobalEval.r * p self.W = r*w + (1-r)*self.W From 704a9b9b4e110016e9d79059c099ed6a8a0d9621 Mon Sep 17 00:00:00 2001 From: Bowen Xu Date: Wed, 8 Nov 2023 19:40:56 -0500 Subject: [PATCH 14/16] modify --- pynars/NARS/Control/Reasoner.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pynars/NARS/Control/Reasoner.py b/pynars/NARS/Control/Reasoner.py index f5edcbb8..bda111bb 100644 --- a/pynars/NARS/Control/Reasoner.py +++ b/pynars/NARS/Control/Reasoner.py @@ -212,6 +212,8 @@ def mental_operation(self, task: Task, concept: Concept, answers_question: Task, if task is not None and task.is_executable: 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 From 1ec9a74185a7e74d2435edae9d3409e989dc4f2b Mon Sep 17 00:00:00 2001 From: Bowen Xu Date: Thu, 9 Nov 2023 14:20:32 -0500 Subject: [PATCH 15/16] fix requirement --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 986f9b1e..7c93e886 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,7 +21,7 @@ miniKanren>=1.0.3 # GUI related packages qt-material>=2.14 qtawesome>=1.2.3 -as-prc>=1.0.1 +as-rpc>=1.0.1 qasync pyqtgraph PySide6 \ No newline at end of file From 390d51d754f3ef3d4daf09863e73a40774989b31 Mon Sep 17 00:00:00 2001 From: Bowen Xu Date: Wed, 6 Dec 2023 21:20:51 -0500 Subject: [PATCH 16/16] call `update_wellbeing()` in `_accept_goal()` --- 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 de0db7cf..32d87b1d 100644 --- a/pynars/NARS/DataStructures/_py/Memory.py +++ b/pynars/NARS/DataStructures/_py/Memory.py @@ -261,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) @@ -271,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) + +