Daniel Sanchez c763d47339
feat: Implement subnetworks assignations algorithm (#130)
* Implement subnetworks assignations algorithm with tests

* Remove main block

* Make shrinking work

* Fix tests, add random increasing decreasing test

* Adapt docs

* Reorg functions

* Cleanup

* Typos

* Add randomness to balance subnetworks

* Fit tests and adapt documentation

* Define shuffling

* Naming fixed

* Raise error on too small network
2025-07-11 09:48:57 +00:00

111 lines
5.0 KiB
Python

import random
from itertools import chain
from typing import List, Counter
from unittest import TestCase
from da.assignations.refill import calculate_subnetwork_assignations, Assignations, DeclarationId
class TestRefill(TestCase):
def test_single_with(self, subnetworks_size = 2048, replication_factor: int = 3, network_size: int = 100):
random_seed = random.randbytes(32)
nodes = [random.randbytes(32) for _ in range(network_size)]
previous_nodes = [set() for _ in range(subnetworks_size)]
assignations = calculate_subnetwork_assignations(nodes, previous_nodes, replication_factor, random_seed)
self.assert_assignations(assignations, nodes, replication_factor)
def test_single_network_sizes(self):
for i in [500, 1000, 10000, 100000]:
with self.subTest(i):
self.test_single_with(network_size=i)
def test_evolving_increasing_network(self):
random_seed = random.randbytes(32)
network_size = 100
replication_factor = 3
nodes = [random.randbytes(32) for _ in range(network_size)]
assignations = [set() for _ in range(2048)]
assignations = calculate_subnetwork_assignations(nodes, assignations, replication_factor, random_seed)
new_nodes = nodes
for network_size in [300, 500, 1000, 10000, 100000]:
random_seed = random.randbytes(32)
new_nodes = self.expand_nodes(new_nodes, network_size - len(nodes))
self.mutate_nodes(new_nodes, network_size//3)
assignations = calculate_subnetwork_assignations(new_nodes, assignations, replication_factor, random_seed)
self.assert_assignations(assignations, new_nodes, replication_factor)
def test_evolving_decreasing_network(self):
random_seed = random.randbytes(32)
network_size = 100000
replication_factor = 3
nodes = [random.randbytes(32) for _ in range(network_size)]
assignations = [set() for _ in range(2048)]
assignations = calculate_subnetwork_assignations(nodes, assignations, replication_factor, random_seed)
new_nodes = nodes
for network_size in reversed([100, 300, 500, 1000, 10000]):
random_seed = random.randbytes(32)
new_nodes = self.shrink_nodes(new_nodes, network_size)
self.mutate_nodes(new_nodes, network_size//3)
assignations = calculate_subnetwork_assignations(new_nodes, assignations, replication_factor, random_seed)
self.assert_assignations(assignations, new_nodes, replication_factor)
def test_random_increase_decrease_network(self):
random_seed = random.randbytes(32)
network_size = 10000
replication_factor = 3
nodes = [random.randbytes(32) for _ in range(network_size)]
assignations = [set() for _ in range(2048)]
assignations = calculate_subnetwork_assignations(nodes, assignations, replication_factor, random_seed)
new_nodes = nodes
for step in (random.randrange(100, 1000) for _ in range(100)):
random_seed = random.randbytes(32)
if bool(random.choice((0, 1))):
network_size += step
new_nodes = self.expand_nodes(new_nodes, network_size)
else:
network_size -= step
new_nodes = self.shrink_nodes(new_nodes, network_size)
self.mutate_nodes(new_nodes, network_size//3)
assignations = calculate_subnetwork_assignations(new_nodes, assignations, replication_factor, random_seed)
self.assert_assignations(assignations, new_nodes, replication_factor)
@classmethod
def mutate_nodes(cls, nodes: List[DeclarationId], count: int):
assert count < len(nodes)
for i in random.choices(list(range(len(nodes))), k=count):
nodes[i] = random.randbytes(32)
@classmethod
def expand_nodes(cls, nodes: List[DeclarationId], count: int) -> List[DeclarationId]:
return [*nodes, *(random.randbytes(32) for _ in range(count))]
@classmethod
def shrink_nodes(cls, nodes: List[DeclarationId], count: int) -> List[DeclarationId]:
return list(random.sample(nodes, k=count))
def assert_assignations(self, assignations: Assignations, nodes: List[DeclarationId], replication_factor: int):
self.assertEqual(
len(set(chain.from_iterable(assignations))),
len(nodes),
"Only active nodes should be assigned"
)
self.assertTrue(
all(len(assignation) >= replication_factor for assignation in assignations),
f"No subnetworks should have less than {replication_factor} nodes"
)
self.assertAlmostEqual(
max(map(len, assignations)),
min(map(len, assignations)),
msg="Subnetwork size variant should not be bigger than 1",
delta=1
)
self.assertAlmostEqual(
len(set(Counter(chain.from_iterable(assignations)).values())),
1,
msg="Nodes should be assigned uniformly to subnetworks",
delta=1,
)