From c6801b5beac93bf980d18c9de687544908060b31 Mon Sep 17 00:00:00 2001 From: Maxim Tarasov Date: Mon, 18 Dec 2023 09:56:37 -0800 Subject: [PATCH 1/5] add OpenNARS-style Distributor in Bag --- Tests/test_NAL/test_NAL2.py | 2 +- Tests/test_NAL/test_NAL5.py | 6 +++-- Tests/test_NAL/test_NAL6.py | 6 ++--- Tests/test_NAL/test_NAL7.py | 2 +- pynars/NARS/DataStructures/_py/Bag.py | 24 +++++++++++------ pynars/NARS/DataStructures/_py/Distributor.py | 26 +++++++++++++++++++ 6 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 pynars/NARS/DataStructures/_py/Distributor.py diff --git a/Tests/test_NAL/test_NAL2.py b/Tests/test_NAL/test_NAL2.py index 84d7a7e7..774288c4 100644 --- a/Tests/test_NAL/test_NAL2.py +++ b/Tests/test_NAL/test_NAL2.py @@ -84,7 +84,7 @@ def test_backward_inference(self): tasks_derived = process_two_premises( ' swimmer>. %1.00;0.90%', '<{?1} --> swimmer>?', - 6 + 20 ) self.assertTrue( output_contains(tasks_derived, '<{?1} --> bird>?') diff --git a/Tests/test_NAL/test_NAL5.py b/Tests/test_NAL/test_NAL5.py index 7c1f928a..e9333a7e 100644 --- a/Tests/test_NAL/test_NAL5.py +++ b/Tests/test_NAL/test_NAL5.py @@ -785,16 +785,18 @@ def test_conditional_deduction_compound_eliminate_0(self): tasks_derived = process_two_premises( '<(&&, [flying]>, [with_wings]>) ==> bird>>. %1.00;0.90%', ' [flying]>. %1.00;0.90%', - 2 + 10 ) self.assertTrue( output_contains(tasks_derived, '< [with_wings]> ==> bird>>. %1.00;0.81%') ) + nars.reset() + tasks_derived = process_two_premises( ' [flying]>. %1.00;0.90%', '<(&&, [flying]>, [with_wings]>) ==> bird>>. %1.00;0.90%', - 2 + 10 ) self.assertTrue( output_contains(tasks_derived, '< [with_wings]> ==> bird>>. %1.00;0.81%') diff --git a/Tests/test_NAL/test_NAL6.py b/Tests/test_NAL/test_NAL6.py index 4eb52662..0b81cb2b 100644 --- a/Tests/test_NAL/test_NAL6.py +++ b/Tests/test_NAL/test_NAL6.py @@ -750,7 +750,7 @@ def test_multiple_variables_introduction_1(self): tasks_derived = process_two_premises( '(&&,<#x --> key>,<{lock1} --> (/,open,#x,_)>). %1.00;0.90%', '<{lock1} --> lock>. %1.00;0.90%', - 10 + 20 ) self.assertTrue( @@ -781,7 +781,7 @@ def test_second_variable_introduction_induction(self): tasks_derived = process_two_premises( '< (/,open,$1,_)> ==> <$1 --> key>>. %1.00;0.90%', ' lock>. %1.00;0.90%', - 10 + 20 ) self.assertTrue( @@ -864,7 +864,7 @@ def test_second_level_variable_unification_1_0(self): tasks_derived = process_two_premises( ' (&&,<#2 --> B>,C)>. %1.00;0.90%', ' B>. %1.00;0.90%', - 10 + 20 ) self.assertTrue( diff --git a/Tests/test_NAL/test_NAL7.py b/Tests/test_NAL/test_NAL7.py index 381f81ce..beae1825 100644 --- a/Tests/test_NAL/test_NAL7.py +++ b/Tests/test_NAL/test_NAL7.py @@ -331,7 +331,7 @@ def test_inference_on_tense_2(self): tasks_derived = process_two_premises( '<<(*,John,key_101) --> hold> =/> <(*,John,room_101) --> enter>>. %1.00;0.90%', '<(*,John,room_101) --> enter>. :\: %1.00;0.90%', - 10 + 30 ) self.assertTrue( diff --git a/pynars/NARS/DataStructures/_py/Bag.py b/pynars/NARS/DataStructures/_py/Bag.py index c511e1a3..841fd989 100644 --- a/pynars/NARS/DataStructures/_py/Bag.py +++ b/pynars/NARS/DataStructures/_py/Bag.py @@ -6,6 +6,7 @@ from pynars.Narsese import Item, Task from pynars.NAL.Functions.BudgetFunctions import * from typing import Union, Callable, Any +from .Distributor import Distributor class Bag: @@ -49,11 +50,18 @@ def __init__(self, capacity: int, n_buckets: int = None, take_in_order: bool = T take_in_order (bool): if True, an item is taken out in order within a bucket, otherwise a random item is taken out. ''' self.capacity = capacity - self.pointer = 0 # Pointing to the Bag's current bucket number self.take_in_order = take_in_order self.item_lut = self.LUT(key=key) # look up table self.n_levels = n_buckets if n_buckets is not None else Config.num_buckets + self.pointer = self.n_levels - 1 # Pointing to the Bag's current bucket number + + self.distributor = Distributor.new(self.n_levels) + self.levels = tuple(list() for i in range(self.n_levels)) # initialize buckets between 0 and capacity + + self.currenCounter = 0 + self.levelIndex = capacity % self.n_levels + # self.buckets = self.Depq(maxlen=self.n_buckets) n_digits = int(math.log10(self.n_levels)) + 3 @@ -66,9 +74,12 @@ def map_priority(priority: float): def take(self, remove = True) -> Item: if len(self) == 0: return None - - if self._is_current_level_empty(): - self._move_to_next_nonempty_level() + if self._is_current_level_empty() or self.currenCounter == 0: + self.pointer = self.distributor.pick(self.levelIndex) + self.levelIndex = self.distributor.next(self.levelIndex) + while self._is_current_level_empty(): + self.pointer = self.distributor.pick(self.levelIndex) + self.levelIndex = self.distributor.next(self.levelIndex) if self.take_in_order: # take the first item from the current bucket @@ -86,10 +97,7 @@ def take(self, remove = True) -> Item: else: item = self.levels[self.pointer][idx] - bucket_probability = self.pointer / self.n_levels - rnd = random.random() # [0.0, 1.0) - if rnd > bucket_probability: - self._move_to_next_nonempty_level() + self.currenCounter = idx return item diff --git a/pynars/NARS/DataStructures/_py/Distributor.py b/pynars/NARS/DataStructures/_py/Distributor.py new file mode 100644 index 00000000..1cb48322 --- /dev/null +++ b/pynars/NARS/DataStructures/_py/Distributor.py @@ -0,0 +1,26 @@ +import functools + + +class Distributor: + @staticmethod + @functools.cache + def new(range_val): + return Distributor(range_val) + + def __init__(self, range_val): + self.capacity = (range_val * (range_val + 1)) // 2 + self.order = [-1] * self.capacity + + index = 0 + for rank in range(range_val, 0, -1): + for time in range(rank): + index = ((self.capacity // rank) + index) % self.capacity + while self.order[index] >= 0: + index = (index + 1) % self.capacity + self.order[index] = rank - 1 + + def pick(self, index): + return self.order[index] + + def next(self, index): + return (index + 1) % self.capacity \ No newline at end of file From a16503fdb0f2936e1ef1c3c6c138c1aa88037030 Mon Sep 17 00:00:00 2001 From: Maxim Tarasov Date: Mon, 18 Dec 2023 10:07:05 -0800 Subject: [PATCH 2/5] use lru-cache instead of cache for backwards compatibility --- pynars/NARS/DataStructures/_py/Distributor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pynars/NARS/DataStructures/_py/Distributor.py b/pynars/NARS/DataStructures/_py/Distributor.py index 1cb48322..bb6407c9 100644 --- a/pynars/NARS/DataStructures/_py/Distributor.py +++ b/pynars/NARS/DataStructures/_py/Distributor.py @@ -3,7 +3,7 @@ class Distributor: @staticmethod - @functools.cache + @functools.lru_cache(maxsize=None) def new(range_val): return Distributor(range_val) From 8d2005edf9b37b6e277c8e5eedd52c1a26ddfd08 Mon Sep 17 00:00:00 2001 From: Maxim Tarasov Date: Tue, 19 Dec 2023 09:53:14 -0800 Subject: [PATCH 3/5] adding comments to Distributor class; modifying test_bag_take_task --- Tests/test_Bag.py | 2 +- pynars/NARS/DataStructures/_py/Distributor.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Tests/test_Bag.py b/Tests/test_Bag.py index 3bb90b14..0b93a0b2 100644 --- a/Tests/test_Bag.py +++ b/Tests/test_Bag.py @@ -90,7 +90,7 @@ def test_bag_take_task(self): cnt1 += 1 elif task == task2: cnt2 += 1 - self.assertGreater(cnt1, 3*cnt2) + self.assertGreater(cnt1, cnt2) bag.take_by_key(task1) bag.take_by_key(task2) diff --git a/pynars/NARS/DataStructures/_py/Distributor.py b/pynars/NARS/DataStructures/_py/Distributor.py index bb6407c9..03b59962 100644 --- a/pynars/NARS/DataStructures/_py/Distributor.py +++ b/pynars/NARS/DataStructures/_py/Distributor.py @@ -1,12 +1,18 @@ -import functools - +from functools import lru_cache +""" +A pseudo-random number generator, used in Bag +""" class Distributor: @staticmethod - @functools.lru_cache(maxsize=None) + @lru_cache(maxsize=None) def new(range_val): + '''Factory method for creating new Distributors with caching to avoid repeated calculations''' return Distributor(range_val) + """ + For any number N < range, there is N+1 copies of it in the array, distributed as evenly as possible + """ def __init__(self, range_val): self.capacity = (range_val * (range_val + 1)) // 2 self.order = [-1] * self.capacity From c1ad5de4f97de7f272da77b109cf9112ab91cc77 Mon Sep 17 00:00:00 2001 From: Maxim Tarasov Date: Tue, 19 Dec 2023 16:23:23 -0800 Subject: [PATCH 4/5] fix typo --- pynars/NARS/DataStructures/_py/Bag.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pynars/NARS/DataStructures/_py/Bag.py b/pynars/NARS/DataStructures/_py/Bag.py index 841fd989..80f3bf63 100644 --- a/pynars/NARS/DataStructures/_py/Bag.py +++ b/pynars/NARS/DataStructures/_py/Bag.py @@ -59,7 +59,7 @@ def __init__(self, capacity: int, n_buckets: int = None, take_in_order: bool = T self.levels = tuple(list() for i in range(self.n_levels)) # initialize buckets between 0 and capacity - self.currenCounter = 0 + self.currentCounter = 0 self.levelIndex = capacity % self.n_levels # self.buckets = self.Depq(maxlen=self.n_buckets) @@ -74,7 +74,7 @@ def map_priority(priority: float): def take(self, remove = True) -> Item: if len(self) == 0: return None - if self._is_current_level_empty() or self.currenCounter == 0: + if self._is_current_level_empty() or self.currentCounter == 0: self.pointer = self.distributor.pick(self.levelIndex) self.levelIndex = self.distributor.next(self.levelIndex) while self._is_current_level_empty(): @@ -97,7 +97,7 @@ def take(self, remove = True) -> Item: else: item = self.levels[self.pointer][idx] - self.currenCounter = idx + self.currentCounter = idx return item From f9c42706a28787c590b6832344777dfc092983eb Mon Sep 17 00:00:00 2001 From: Maxim Tarasov Date: Thu, 21 Dec 2023 09:45:19 -0800 Subject: [PATCH 5/5] style fixes --- pynars/NARS/DataStructures/_py/Bag.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pynars/NARS/DataStructures/_py/Bag.py b/pynars/NARS/DataStructures/_py/Bag.py index 80f3bf63..7f676fed 100644 --- a/pynars/NARS/DataStructures/_py/Bag.py +++ b/pynars/NARS/DataStructures/_py/Bag.py @@ -59,8 +59,8 @@ def __init__(self, capacity: int, n_buckets: int = None, take_in_order: bool = T self.levels = tuple(list() for i in range(self.n_levels)) # initialize buckets between 0 and capacity - self.currentCounter = 0 - self.levelIndex = capacity % self.n_levels + self.current_counter = 0 + self.level_index = capacity % self.n_levels # self.buckets = self.Depq(maxlen=self.n_buckets) n_digits = int(math.log10(self.n_levels)) + 3 @@ -74,12 +74,12 @@ def map_priority(priority: float): def take(self, remove = True) -> Item: if len(self) == 0: return None - if self._is_current_level_empty() or self.currentCounter == 0: - self.pointer = self.distributor.pick(self.levelIndex) - self.levelIndex = self.distributor.next(self.levelIndex) + if self._is_current_level_empty() or self.current_counter == 0: + self.pointer = self.distributor.pick(self.level_index) + self.level_index = self.distributor.next(self.level_index) while self._is_current_level_empty(): - self.pointer = self.distributor.pick(self.levelIndex) - self.levelIndex = self.distributor.next(self.levelIndex) + self.pointer = self.distributor.pick(self.level_index) + self.level_index = self.distributor.next(self.level_index) if self.take_in_order: # take the first item from the current bucket @@ -97,7 +97,7 @@ def take(self, remove = True) -> Item: else: item = self.levels[self.pointer][idx] - self.currentCounter = idx + self.current_counter = idx return item