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/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..7f676fed 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.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 @@ -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.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.level_index) + self.level_index = self.distributor.next(self.level_index) 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.current_counter = 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..03b59962 --- /dev/null +++ b/pynars/NARS/DataStructures/_py/Distributor.py @@ -0,0 +1,32 @@ +from functools import lru_cache + +""" +A pseudo-random number generator, used in Bag +""" +class Distributor: + @staticmethod + @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 + + 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