final presentation, restructure of the repository, updated code to have weighted probabilities and more
This commit is contained in:
@@ -2,10 +2,10 @@ FROM python:3.14-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./main.py /app
|
||||
COPY ./simulator.py /app
|
||||
COPY ./requirements.txt /app
|
||||
COPY requirements.txt /app/
|
||||
|
||||
RUN pip install -r requirements.txt
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY main.py simulator.py /app/
|
||||
|
||||
CMD ["python", "main.py"]
|
||||
|
||||
298
code/main.py
298
code/main.py
@@ -30,18 +30,6 @@ def Continue(title="") -> None:
|
||||
print()
|
||||
|
||||
|
||||
# def MainMenu() -> None:
|
||||
# global currentFunction
|
||||
# options = ["Simulator", "Calculator", "Quit"]
|
||||
# actions: list[Callable[[], None]] = [SimulatorMenu, StopApplication]
|
||||
|
||||
# selectedIndex = ShowMenu(
|
||||
# options, title="Boolean Network Simulator\nby Tom Zuidberg"
|
||||
# )
|
||||
|
||||
# currentFunction = actions[selectedIndex]
|
||||
|
||||
|
||||
def StopApplication() -> None:
|
||||
quit()
|
||||
|
||||
@@ -151,11 +139,14 @@ def BooleanNetworkMenu(
|
||||
functionStrings: list = [None for _ in range(3)]
|
||||
functionsDirtyFlag = False
|
||||
current_highlight = 0
|
||||
functions_highlight = 0
|
||||
probFunctionStrings: list[list[str]] = [[] for _ in range(3)]
|
||||
|
||||
if bn is not None and funcStrings is not None and funcs is not None:
|
||||
boolNetwork = bn
|
||||
functions = funcs
|
||||
functionStrings = funcStrings
|
||||
probFunctionStrings = [[] for _ in range(boolNetwork.size)]
|
||||
|
||||
def Menu() -> None:
|
||||
nonlocal current_highlight
|
||||
@@ -164,10 +155,7 @@ def BooleanNetworkMenu(
|
||||
f"Set size (current: {boolNetwork.size}) WARNING: this will reset all other options!",
|
||||
f"Set state (current: {str(boolNetwork)[-boolNetwork.size :]})",
|
||||
f"Set update scheme (current: {boolNetwork.updateScheme})",
|
||||
*[
|
||||
f"Set update function of node x{i} (current: {functionStrings[i - 1]})"
|
||||
for i in range(1, boolNetwork.size + 1)
|
||||
],
|
||||
"Edit update functions",
|
||||
None,
|
||||
"Update once (hold ENTER for continuous updates)",
|
||||
"Update multiple times",
|
||||
@@ -182,7 +170,7 @@ def BooleanNetworkMenu(
|
||||
SetSizeHelper,
|
||||
SetStateHelper,
|
||||
SetUpdateSchemeHelper,
|
||||
*[partial(SetFunctionHelper, i) for i in range(boolNetwork.size)],
|
||||
FunctionsMenu,
|
||||
None,
|
||||
UpdateHelper,
|
||||
MultiUpdateHelper,
|
||||
@@ -204,7 +192,7 @@ def BooleanNetworkMenu(
|
||||
actions[selectedIndex]()
|
||||
|
||||
def SetSizeHelper() -> None:
|
||||
nonlocal boolNetwork, functions, functionStrings
|
||||
nonlocal boolNetwork, functions, functionStrings, probFunctionStrings
|
||||
while True:
|
||||
size = input("Set new size (Leave empty to cancel):\n").strip()
|
||||
if size == "":
|
||||
@@ -219,6 +207,7 @@ def BooleanNetworkMenu(
|
||||
boolNetwork = BooleanNetwork(size)
|
||||
functions = [None for _ in range(size)]
|
||||
functionStrings = [None for _ in range(size)]
|
||||
probFunctionStrings = [[] for _ in range(size)]
|
||||
return
|
||||
|
||||
def SetStateHelper() -> None:
|
||||
@@ -237,9 +226,32 @@ def BooleanNetworkMenu(
|
||||
print("Invalid input:", e)
|
||||
|
||||
def SetUpdateSchemeHelper() -> None:
|
||||
nonlocal boolNetwork
|
||||
nonlocal boolNetwork, functions, functionStrings, functionsDirtyFlag
|
||||
|
||||
def AdoptFromProbabilistic() -> None:
|
||||
nonlocal functionsDirtyFlag
|
||||
|
||||
for i in range(boolNetwork.size):
|
||||
if probFunctionStrings[i]:
|
||||
funcString = probFunctionStrings[i][0]
|
||||
func = eval(
|
||||
"lambda "
|
||||
+ ",".join(f"x{j}" for j in range(1, boolNetwork.size + 1))
|
||||
+ ":"
|
||||
+ funcString
|
||||
)
|
||||
functions[i] = func
|
||||
functionStrings[i] = funcString
|
||||
functionsDirtyFlag = True
|
||||
|
||||
def SwitchToSynchronous() -> None:
|
||||
wasProbabilistic = boolNetwork.updateScheme == "probabilistic"
|
||||
boolNetwork.UseSynchronousScheme()
|
||||
if wasProbabilistic:
|
||||
AdoptFromProbabilistic()
|
||||
|
||||
def SetSequentialHelper() -> None:
|
||||
wasProbabilistic = boolNetwork.updateScheme == "probabilistic"
|
||||
while True:
|
||||
seq = input(
|
||||
"Set sequence. (Leave empty to cancel)\nFormat example: '1,4,3,2'\n"
|
||||
@@ -252,26 +264,24 @@ def BooleanNetworkMenu(
|
||||
try:
|
||||
seq = [int(i) for i in seq]
|
||||
boolNetwork.UseSequentialScheme(seq)
|
||||
if wasProbabilistic:
|
||||
AdoptFromProbabilistic()
|
||||
return
|
||||
except Exception as e:
|
||||
print("Invalid input:", e)
|
||||
|
||||
def SetProbabilisticHelper() -> None:
|
||||
while True:
|
||||
chance = input(
|
||||
"Set flip chance as float between 0.0 and 1.0. (Leave empty to cancel)\n"
|
||||
).strip()
|
||||
if chance == "":
|
||||
print("Cancelled")
|
||||
return
|
||||
def SwitchToAsyncRandom() -> None:
|
||||
wasProbabilistic = boolNetwork.updateScheme == "probabilistic"
|
||||
boolNetwork.UseAsynchronousRandomScheme()
|
||||
if wasProbabilistic:
|
||||
AdoptFromProbabilistic()
|
||||
|
||||
try:
|
||||
chance = float(chance)
|
||||
boolNetwork.UseProbabilisticScheme(chance)
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
print("Invalid input:", e)
|
||||
def SwitchToProbabilistic() -> None:
|
||||
for i in range(boolNetwork.size):
|
||||
if not probFunctionStrings[i] and functions[i] is not None:
|
||||
boolNetwork.AddProbabilisticFunction(i, functions[i], 1.0)
|
||||
probFunctionStrings[i].append(functionStrings[i])
|
||||
boolNetwork.UseProbabilisticScheme()
|
||||
|
||||
title = "Select update scheme:"
|
||||
options = [
|
||||
@@ -282,15 +292,120 @@ def BooleanNetworkMenu(
|
||||
"Cancel",
|
||||
]
|
||||
actions = [
|
||||
lambda: boolNetwork.UseSynchronousScheme(),
|
||||
SwitchToSynchronous,
|
||||
SetSequentialHelper,
|
||||
SetProbabilisticHelper,
|
||||
lambda: boolNetwork.UseAsynchronousRandomScheme(),
|
||||
SwitchToProbabilistic,
|
||||
SwitchToAsyncRandom,
|
||||
lambda: None,
|
||||
]
|
||||
|
||||
actions[ShowMenu(options=options, title=title)]()
|
||||
|
||||
def FunctionsMenu() -> None:
|
||||
nonlocal functions_highlight
|
||||
done: bool = False
|
||||
|
||||
def Menu() -> None:
|
||||
nonlocal functions_highlight
|
||||
title = "Edit update functions:"
|
||||
deleteTargets: list = []
|
||||
|
||||
if boolNetwork.updateScheme in ("synchronous", "sequential", None):
|
||||
options = [
|
||||
*[
|
||||
f"Set update function of node x{i} (current: {functionStrings[i - 1]})"
|
||||
for i in range(1, boolNetwork.size + 1)
|
||||
],
|
||||
None,
|
||||
"Return",
|
||||
]
|
||||
actions = [
|
||||
*[partial(SetFunctionHelper, i) for i in range(boolNetwork.size)],
|
||||
None,
|
||||
ReturnHelper,
|
||||
]
|
||||
elif boolNetwork.updateScheme == "asynchronous_random":
|
||||
options = [
|
||||
*[
|
||||
f"Set update function of node x{i} (current: {functionStrings[i - 1]})"
|
||||
for i in range(1, boolNetwork.size + 1)
|
||||
],
|
||||
None,
|
||||
*[
|
||||
f"Set selection weight of node x{i} (current: {boolNetwork.node_selection_weights[i - 1]})"
|
||||
for i in range(1, boolNetwork.size + 1)
|
||||
],
|
||||
None,
|
||||
"Return",
|
||||
]
|
||||
actions = [
|
||||
*[partial(SetFunctionHelper, i) for i in range(boolNetwork.size)],
|
||||
None,
|
||||
*[partial(SetNodeWeightHelper, i) for i in range(boolNetwork.size)],
|
||||
None,
|
||||
ReturnHelper,
|
||||
]
|
||||
else: # probabilistic
|
||||
title = "Edit update functions:\n(highlight a function entry and press 'd' to delete it)"
|
||||
options = []
|
||||
actions = []
|
||||
for i in range(boolNetwork.size):
|
||||
for j in range(len(probFunctionStrings[i])):
|
||||
options.append(
|
||||
f"Set update function #{j + 1} of node x{i + 1} (current: {probFunctionStrings[i][j]})"
|
||||
)
|
||||
actions.append(partial(EditProbFunctionHelper, i, j))
|
||||
deleteTargets.append((i, j))
|
||||
options.append(
|
||||
f"Set weight of function #{j + 1} of node x{i + 1} (current: {boolNetwork.probabilistic_weights[i][j]})"
|
||||
)
|
||||
actions.append(partial(EditProbWeightHelper, i, j))
|
||||
deleteTargets.append(None)
|
||||
options.append(f"Add new function for node x{i + 1}")
|
||||
actions.append(partial(AddProbFunctionHelper, i))
|
||||
deleteTargets.append(None)
|
||||
options.append(None)
|
||||
actions.append(None)
|
||||
deleteTargets.append(None)
|
||||
options.append("Return")
|
||||
actions.append(ReturnHelper)
|
||||
deleteTargets.append(None)
|
||||
|
||||
if boolNetwork.updateScheme == "probabilistic":
|
||||
menu = TerminalMenu(
|
||||
menu_entries=options,
|
||||
title=title,
|
||||
multi_select=False,
|
||||
cursor_index=functions_highlight,
|
||||
accept_keys=("enter", "d"),
|
||||
)
|
||||
selectedIndex = menu.show()
|
||||
if selectedIndex is None:
|
||||
selectedIndex = len(options) - 1
|
||||
acceptKey = "enter"
|
||||
else:
|
||||
acceptKey = menu.chosen_accept_key
|
||||
functions_highlight = selectedIndex
|
||||
|
||||
if acceptKey == "d" and deleteTargets[selectedIndex] is not None:
|
||||
DeleteProbFunctionHelper(*deleteTargets[selectedIndex])
|
||||
else:
|
||||
actions[selectedIndex]()
|
||||
return
|
||||
|
||||
selectedIndex = ShowMenu(
|
||||
options=options, title=title, highlight_entry=functions_highlight
|
||||
)
|
||||
functions_highlight = selectedIndex
|
||||
actions[selectedIndex]()
|
||||
|
||||
def ReturnHelper() -> None:
|
||||
nonlocal done
|
||||
done = True
|
||||
|
||||
while not done:
|
||||
Menu()
|
||||
|
||||
def UpdateHelper() -> None:
|
||||
nonlocal functions, boolNetwork, functionsDirtyFlag
|
||||
if functionsDirtyFlag:
|
||||
@@ -377,6 +492,115 @@ def BooleanNetworkMenu(
|
||||
except Exception as e:
|
||||
print("Error while parsing function:", e)
|
||||
|
||||
def SetNodeWeightHelper(index: int) -> None:
|
||||
nonlocal boolNetwork
|
||||
while True:
|
||||
weight = input(
|
||||
f"Set new selection weight for node x{index + 1}. Must be a positive number. (Leave empty to cancel)\n"
|
||||
).strip()
|
||||
if weight == "":
|
||||
print("Cancelled")
|
||||
return
|
||||
try:
|
||||
weight = float(weight)
|
||||
boolNetwork.SetNodeSelectionWeight(index, weight)
|
||||
return
|
||||
except Exception as e:
|
||||
print("Invalid input:", e)
|
||||
|
||||
def AddProbFunctionHelper(index: int) -> None:
|
||||
nonlocal boolNetwork, probFunctionStrings
|
||||
while True:
|
||||
funcString = input(
|
||||
f"Set new function for node x{index + 1}. The function will receive all nodes in form of x1, x2, ..., x[size]. (Leave empty to cancel)\n"
|
||||
)
|
||||
if funcString == "":
|
||||
print("Cancelled")
|
||||
return
|
||||
try:
|
||||
func = (
|
||||
"lambda "
|
||||
+ ",".join(f"x{i}" for i in range(1, boolNetwork.size + 1))
|
||||
+ ":"
|
||||
+ funcString
|
||||
)
|
||||
func = eval(func)
|
||||
if not isinstance(func, FunctionType):
|
||||
print("Please enter a valid function. Got: " + func)
|
||||
continue
|
||||
except Exception as e:
|
||||
print("Error while parsing function:", e)
|
||||
continue
|
||||
|
||||
weight = 1.0
|
||||
weightInput = input(
|
||||
"Set weight for this function. Must be a positive number. (Leave empty for default 1.0)\n"
|
||||
).strip()
|
||||
if weightInput != "":
|
||||
try:
|
||||
weight = float(weightInput)
|
||||
except ValueError:
|
||||
print("Invalid weight, using default 1.0")
|
||||
weight = 1.0
|
||||
|
||||
try:
|
||||
boolNetwork.AddProbabilisticFunction(index, func, weight)
|
||||
probFunctionStrings[index].append(funcString)
|
||||
return
|
||||
except Exception as e:
|
||||
print("Error while adding function:", e)
|
||||
|
||||
def EditProbFunctionHelper(index: int, function_index: int) -> None:
|
||||
nonlocal boolNetwork, probFunctionStrings
|
||||
while True:
|
||||
funcString = input(
|
||||
f"Set new function for node x{index + 1}, function #{function_index + 1}. (Leave empty to cancel)\n"
|
||||
)
|
||||
if funcString == "":
|
||||
print("Cancelled")
|
||||
return
|
||||
try:
|
||||
func = (
|
||||
"lambda "
|
||||
+ ",".join(f"x{i}" for i in range(1, boolNetwork.size + 1))
|
||||
+ ":"
|
||||
+ funcString
|
||||
)
|
||||
func = eval(func)
|
||||
if not isinstance(func, FunctionType):
|
||||
print("Please enter a valid function. Got: " + func)
|
||||
continue
|
||||
boolNetwork.SetProbabilisticFunction(index, function_index, func)
|
||||
probFunctionStrings[index][function_index] = funcString
|
||||
return
|
||||
except Exception as e:
|
||||
print("Error while parsing function:", e)
|
||||
|
||||
def EditProbWeightHelper(index: int, function_index: int) -> None:
|
||||
nonlocal boolNetwork
|
||||
while True:
|
||||
weight = input(
|
||||
f"Set new weight for node x{index + 1}, function #{function_index + 1}. Must be a positive number. (Leave empty to cancel)\n"
|
||||
).strip()
|
||||
if weight == "":
|
||||
print("Cancelled")
|
||||
return
|
||||
try:
|
||||
weight = float(weight)
|
||||
boolNetwork.SetProbabilisticFunctionWeight(index, function_index, weight)
|
||||
return
|
||||
except Exception as e:
|
||||
print("Invalid input:", e)
|
||||
|
||||
def DeleteProbFunctionHelper(index: int, function_index: int) -> None:
|
||||
nonlocal boolNetwork, probFunctionStrings
|
||||
try:
|
||||
boolNetwork.RemoveProbabilisticFunction(index, function_index)
|
||||
del probFunctionStrings[index][function_index]
|
||||
except Exception as e:
|
||||
print("Error while deleting function:", e)
|
||||
Continue()
|
||||
|
||||
def ToggleWriteToFileHelper():
|
||||
nonlocal writeToFile
|
||||
writeToFile = not writeToFile
|
||||
|
||||
@@ -7,6 +7,19 @@ import numpy as np
|
||||
import scipy.linalg
|
||||
|
||||
|
||||
def _wrap_bool_function(
|
||||
function: Callable[Concatenate[bool, ...], bool],
|
||||
) -> Callable[Concatenate[bool, ...], bool]:
|
||||
def wrap(*args, **kwargs) -> bool:
|
||||
result = function(*args, **kwargs)
|
||||
assert type(result) is bool, (
|
||||
f"Function error: Boolean network functions must always return a bool, however got type {type(result)}, {result=}"
|
||||
)
|
||||
return result
|
||||
|
||||
return wrap
|
||||
|
||||
|
||||
class BooleanNetwork:
|
||||
def __init__(self, size: int) -> None:
|
||||
assert type(size) is int and size > 0, (
|
||||
@@ -17,8 +30,7 @@ class BooleanNetwork:
|
||||
self.__has_update_functions = False
|
||||
self.__has_update_scheme = False
|
||||
self.__has_sequence = False
|
||||
self.__has_flip_chance = False
|
||||
self.flip_chance: float = 0
|
||||
self.__has_probabilistic_functions = False
|
||||
self.sequence: list[int] = list()
|
||||
self.seed: int | None = None
|
||||
self.time_step = 0
|
||||
@@ -29,21 +41,23 @@ class BooleanNetwork:
|
||||
lambda x: x for _ in range(size)
|
||||
]
|
||||
|
||||
# asynchronous_random: relative likelihood that node i is the one picked
|
||||
# to update on a given time step. Doesn't need to sum to 1 - it is
|
||||
# normalised (weight_i / sum(weights)) whenever it is used.
|
||||
self.node_selection_weights: list[float] = [1.0 for _ in range(size)]
|
||||
|
||||
# probabilistic: each node may have any number of candidate update
|
||||
# functions. On every update, one candidate per node is drawn
|
||||
# according to its weight (again normalised at use-time, not
|
||||
# required to sum to 1) and applied synchronously.
|
||||
self.probabilistic_functions: list[
|
||||
list[Callable[Concatenate[bool, ...], bool]]
|
||||
] = [list() for _ in range(size)]
|
||||
self.probabilistic_weights: list[list[float]] = [list() for _ in range(size)]
|
||||
|
||||
def SetFunctions(
|
||||
self, functions: Iterable[Callable[Concatenate[bool, ...], bool]]
|
||||
) -> Self:
|
||||
def wrapper(
|
||||
function: Callable[Concatenate[bool, ...], bool],
|
||||
) -> Callable[Concatenate[bool, ...], bool]:
|
||||
def wrap(*args, **kwargs) -> bool:
|
||||
result = function(*args, **kwargs)
|
||||
assert type(result) is bool, (
|
||||
f"Function error: Boolean network functions must always return a bool, however got type {type(result)}, {result=}"
|
||||
)
|
||||
return result
|
||||
|
||||
return wrap
|
||||
|
||||
funcs: list[Callable[Concatenate[bool, ...], bool]] = list(functions)
|
||||
|
||||
assert len(funcs) == self.size, (
|
||||
@@ -56,7 +70,7 @@ class BooleanNetwork:
|
||||
f"Function error: Function arg amount mismatch. Given function takes {len(inspect.signature(func).parameters)} arguments, expected {self.size}"
|
||||
)
|
||||
|
||||
self.functions[i] = wrapper(func)
|
||||
self.functions[i] = _wrap_bool_function(func)
|
||||
|
||||
self.__has_update_functions = True
|
||||
return self
|
||||
@@ -64,22 +78,101 @@ class BooleanNetwork:
|
||||
def SetFunction(
|
||||
self, index: int, function: Callable[Concatenate[bool, ...], bool]
|
||||
) -> Self:
|
||||
def wrapper(
|
||||
function: Callable[Concatenate[bool, ...], bool],
|
||||
) -> Callable[Concatenate[bool, ...], bool]:
|
||||
def wrap(*args, **kwargs) -> bool:
|
||||
result = function(*args, **kwargs)
|
||||
assert type(result) is bool, (
|
||||
f"Function error: Boolean network functions must always return a bool, however got type {type(result)}, {result=}"
|
||||
)
|
||||
return result
|
||||
|
||||
return wrap
|
||||
|
||||
assert 0 <= index < self.size, (
|
||||
f"Function error: cannot set function at index {index} - out of bound."
|
||||
)
|
||||
self.functions[index] = wrapper(function)
|
||||
assert len(inspect.signature(function).parameters) == self.size, (
|
||||
f"Function error: Function arg amount mismatch. Given function takes {len(inspect.signature(function).parameters)} arguments, expected {self.size}"
|
||||
)
|
||||
self.functions[index] = _wrap_bool_function(function)
|
||||
return self
|
||||
|
||||
def SetNodeSelectionWeight(self, index: int, weight: float) -> Self:
|
||||
assert 0 <= index < self.size, (
|
||||
f"Weight error: cannot set selection weight at index {index} - out of bound."
|
||||
)
|
||||
assert type(weight) is float and weight > 0, (
|
||||
f"Weight error: weight must be a positive float. got {weight=}"
|
||||
)
|
||||
self.node_selection_weights[index] = weight
|
||||
return self
|
||||
|
||||
def AddProbabilisticFunction(
|
||||
self,
|
||||
index: int,
|
||||
function: Callable[Concatenate[bool, ...], bool],
|
||||
weight: float = 1.0,
|
||||
) -> Self:
|
||||
assert 0 <= index < self.size, (
|
||||
f"Function error: cannot add function at index {index} - out of bound."
|
||||
)
|
||||
assert len(inspect.signature(function).parameters) == self.size, (
|
||||
f"Function error: Function arg amount mismatch. Given function takes {len(inspect.signature(function).parameters)} arguments, expected {self.size}"
|
||||
)
|
||||
assert type(weight) is float and weight > 0, (
|
||||
f"Weight error: weight must be a positive float. got {weight=}"
|
||||
)
|
||||
|
||||
self.probabilistic_functions[index].append(_wrap_bool_function(function))
|
||||
self.probabilistic_weights[index].append(weight)
|
||||
self.__has_probabilistic_functions = all(
|
||||
len(functions) > 0 for functions in self.probabilistic_functions
|
||||
)
|
||||
return self
|
||||
|
||||
def SetProbabilisticFunction(
|
||||
self,
|
||||
index: int,
|
||||
function_index: int,
|
||||
function: Callable[Concatenate[bool, ...], bool],
|
||||
) -> Self:
|
||||
assert 0 <= index < self.size, (
|
||||
f"Function error: cannot set function at index {index} - out of bound."
|
||||
)
|
||||
assert 0 <= function_index < len(self.probabilistic_functions[index]), (
|
||||
f"Function error: node {index} has no function at position {function_index}."
|
||||
)
|
||||
assert len(inspect.signature(function).parameters) == self.size, (
|
||||
f"Function error: Function arg amount mismatch. Given function takes {len(inspect.signature(function).parameters)} arguments, expected {self.size}"
|
||||
)
|
||||
|
||||
self.probabilistic_functions[index][function_index] = _wrap_bool_function(
|
||||
function
|
||||
)
|
||||
return self
|
||||
|
||||
def SetProbabilisticFunctionWeight(
|
||||
self, index: int, function_index: int, weight: float
|
||||
) -> Self:
|
||||
assert 0 <= index < self.size, (
|
||||
f"Weight error: cannot set weight at index {index} - out of bound."
|
||||
)
|
||||
assert 0 <= function_index < len(self.probabilistic_weights[index]), (
|
||||
f"Weight error: node {index} has no function at position {function_index}."
|
||||
)
|
||||
assert type(weight) is float and weight > 0, (
|
||||
f"Weight error: weight must be a positive float. got {weight=}"
|
||||
)
|
||||
|
||||
self.probabilistic_weights[index][function_index] = weight
|
||||
return self
|
||||
|
||||
def RemoveProbabilisticFunction(self, index: int, function_index: int) -> Self:
|
||||
assert 0 <= index < self.size, (
|
||||
f"Function error: cannot remove function at index {index} - out of bound."
|
||||
)
|
||||
assert 0 <= function_index < len(self.probabilistic_functions[index]), (
|
||||
f"Function error: node {index} has no function at position {function_index}."
|
||||
)
|
||||
assert len(self.probabilistic_functions[index]) > 1, (
|
||||
f"Function error: node {index} must keep at least one probabilistic function."
|
||||
)
|
||||
|
||||
del self.probabilistic_functions[index][function_index]
|
||||
del self.probabilistic_weights[index][function_index]
|
||||
self.__has_probabilistic_functions = all(
|
||||
len(functions) > 0 for functions in self.probabilistic_functions
|
||||
)
|
||||
return self
|
||||
|
||||
def UseSynchronousScheme(self) -> Self:
|
||||
@@ -127,16 +220,9 @@ class BooleanNetwork:
|
||||
self.__has_update_scheme = True
|
||||
return self
|
||||
|
||||
def UseProbabilisticScheme(self, flip_chance: float) -> Self:
|
||||
if flip_chance is not None:
|
||||
assert type(flip_chance) is float, (
|
||||
f"Probabilistic error: given flip_chance is not a float: got {flip_chance}"
|
||||
)
|
||||
self.flip_chance = flip_chance
|
||||
|
||||
def UseProbabilisticScheme(self) -> Self:
|
||||
self.updateScheme = "probabilistic"
|
||||
self.__has_update_scheme = True
|
||||
self.__has_flip_chance = True
|
||||
return self
|
||||
|
||||
def __synchronous_update(self) -> None:
|
||||
@@ -150,15 +236,21 @@ class BooleanNetwork:
|
||||
self.nodes[i] = self.functions[i](*self.nodes)
|
||||
|
||||
def __asynchronous_random_update(self) -> None:
|
||||
index = random.randrange(0, self.size)
|
||||
index = random.choices(
|
||||
range(self.size), weights=self.node_selection_weights, k=1
|
||||
)[0]
|
||||
self.nodes[index] = self.functions[index](*self.nodes)
|
||||
|
||||
def __probabilistic_update(self) -> None:
|
||||
self.__synchronous_update()
|
||||
temp = list()
|
||||
for i in range(self.size):
|
||||
rng = random.random()
|
||||
if rng <= self.flip_chance:
|
||||
self.nodes[i] = not self.nodes[i]
|
||||
chosen = random.choices(
|
||||
self.probabilistic_functions[i],
|
||||
weights=self.probabilistic_weights[i],
|
||||
k=1,
|
||||
)[0]
|
||||
temp.append(chosen(*self.nodes))
|
||||
self.nodes = temp
|
||||
|
||||
def SetState(self, state: str | list[bool] | tuple[bool, ...]) -> Self:
|
||||
assert isinstance(state, (str, list, tuple)), (
|
||||
@@ -192,7 +284,9 @@ class BooleanNetwork:
|
||||
assert type(n) is int and n >= 0, (
|
||||
f"Update error: amount of updates must be an integer and positive. got {n=}"
|
||||
)
|
||||
assert self.__has_update_functions, "Update error: no update functions defined"
|
||||
assert self.updateScheme == "probabilistic" or self.__has_update_functions, (
|
||||
"Update error: no update functions defined"
|
||||
)
|
||||
assert self.__has_update_scheme, "Update error: no update scheme defined"
|
||||
assert type(verbose) is bool, "Update error: verbose must be a bool"
|
||||
assert type(writeToFile) is bool, "Update error: writeToFile must be a bool"
|
||||
@@ -208,7 +302,9 @@ class BooleanNetwork:
|
||||
case "asynchronous_random":
|
||||
selected_update = self.__asynchronous_random_update
|
||||
case "probabilistic":
|
||||
assert self.__has_flip_chance, "Update error: no flip_chance defined"
|
||||
assert self.__has_probabilistic_functions, (
|
||||
"Update error: no probabilistic functions defined for every node"
|
||||
)
|
||||
selected_update = self.__probabilistic_update
|
||||
case _:
|
||||
raise Exception("Update error: update scheme selection went wrong")
|
||||
@@ -261,36 +357,37 @@ class BooleanNetwork:
|
||||
matrix: np.ndarray = np.zeros((dimension, dimension))
|
||||
|
||||
if self.updateScheme == "probabilistic":
|
||||
flipChance = self.flip_chance
|
||||
self.UseSynchronousScheme()
|
||||
for i, state in enumerate(product((False, True), repeat=self.size)):
|
||||
self.SetState(state)
|
||||
self.Update()
|
||||
for flips in product((False, True), repeat=self.size):
|
||||
flipped = int(
|
||||
"".join(
|
||||
str(
|
||||
int(
|
||||
self.nodes[j] if not flips[j] else not self.nodes[j]
|
||||
)
|
||||
)
|
||||
for j in range(self.size)
|
||||
),
|
||||
2,
|
||||
)
|
||||
choice_ranges = [
|
||||
range(len(self.probabilistic_functions[n]))
|
||||
for n in range(self.size)
|
||||
]
|
||||
for combo in product(*choice_ranges):
|
||||
prob = np.float64(1)
|
||||
for flip in flips:
|
||||
prob *= flipChance if flip else 1 - flipChance
|
||||
matrix[i][flipped] = prob
|
||||
self.UseProbabilisticScheme(flipChance)
|
||||
result: list[bool] = []
|
||||
for n in range(self.size):
|
||||
weights = self.probabilistic_weights[n]
|
||||
total_weight = sum(weights)
|
||||
chosen_index = combo[n]
|
||||
prob *= weights[chosen_index] / total_weight
|
||||
result.append(
|
||||
self.probabilistic_functions[n][chosen_index](*state)
|
||||
)
|
||||
result_index = int(
|
||||
"".join(str(int(b)) for b in result), 2
|
||||
)
|
||||
matrix[i][result_index] += prob
|
||||
return matrix
|
||||
|
||||
if self.updateScheme == "asynchronous_random":
|
||||
total_weight = sum(self.node_selection_weights)
|
||||
for i, state in enumerate(product((False, True), repeat=self.size)):
|
||||
for j in range(self.size):
|
||||
self.SetState(state)
|
||||
self.nodes[j] = self.functions[j](*self.nodes)
|
||||
matrix[i][int(self.state, 2)] += np.float64(1) / self.size
|
||||
matrix[i][int(self.state, 2)] += (
|
||||
np.float64(self.node_selection_weights[j]) / total_weight
|
||||
)
|
||||
return matrix
|
||||
|
||||
for i, state in enumerate(product((False, True), repeat=self.size)):
|
||||
|
||||
Reference in New Issue
Block a user