From b96e5f7e38d78620744eaa3464d46bbf3863b830 Mon Sep 17 00:00:00 2001 From: Tom Zuidberg Date: Mon, 22 Jun 2026 02:54:49 +0200 Subject: [PATCH] complete overhaul of python script to simulate boolean networks. not fully bug tested! --- __pycache__/bn_sim.cpython-314.pyc | Bin 4646 -> 0 bytes bn_sim.py | 104 ------ code/__pycache__/simulator.cpython-314.pyc | Bin 0 -> 22738 bytes code/main.py | 400 +++++++++++++++++++++ code/requirements.txt | 3 + code/simulator.py | 299 +++++++++++++++ 6 files changed, 702 insertions(+), 104 deletions(-) delete mode 100644 __pycache__/bn_sim.cpython-314.pyc delete mode 100644 bn_sim.py create mode 100644 code/__pycache__/simulator.cpython-314.pyc create mode 100644 code/main.py create mode 100644 code/requirements.txt create mode 100644 code/simulator.py diff --git a/__pycache__/bn_sim.cpython-314.pyc b/__pycache__/bn_sim.cpython-314.pyc deleted file mode 100644 index df020ed28ec054f4dc1f28b4c080e0b0bd2fe7e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4646 zcmcIn-A^3X6~A{rmRa_LWx-zK+Ux?3F*Ym#9D@iUbsLCX97HGfl2*j41;%(&_QQ8( z9TP>>DpI43zzLG06e}e1OQl+Aq(1hcmD2tN>zXEJN~%=Ni{CKxC4K5ScXnoXSwyX> zz2YA3-h1ZUdw;)k&YU^p_tg_<{<)qvrW8W{fj{i#Dh+m9DIs}sm8kS0xj+pX#<6fw zxF8rpn5bfis1kG;x+_GYk}j%sFjk4GQA*yFUtXic9t_pis zRd0o5v1-*9A_F3vNRATSrTRm}s8bt2Drx|vTWthcuLePS)FzN#wHc&OJp|IP9tPR) zdO!^gy++79Aa#tq-q1$QmW~d0_jK^yb>uP5by2e}%QEx1IRl0+mPpTMQgc%IrUZ~A zBJ|w=h&+kW&eGy((n0Ri$@}EKWD2+Xp!Y)`fPPj-11F)G7kY0dSnu^rO6xT;sos~9 zhN@*VdeX?Sq^9@2tfjRcRa?j{CSYc%XHqlX%&?#7J+Er1WE!5FO~>_QDwVqb1wEb?;zg_yH|T+?7w3HIp4)}k|+0s_3?YNpT1BF+W&rAe!U|$gp&&}g7(de zRet6;K#41%GVY#|tHWTnt36oAR|>dQR^WOkXmcG?IzMxNam2&7?^kcQ_g0;cQB{%GzP( zICT9v5P9mvmq+q1ZTW&94t$uq zGyJ8mwLrImp*8x^sr9j9u)QEH*F6!5Km3h1@XlDR8#-uOZdkrmjM!L|=VtzcBcT(I z!{xwvB{(NaxtkJ$+R^ z3RWC;VpJ8n;S_eOwO_faimEhNN)f;tfU3&i7p}LypJke^N5&>EkEszO6M?E5i*hZJ zNox_^(6Z5prO|n#Gcv1P%P_61tldf)CcW`5Dbh0vSJDXJOU4xGN+~L9j@4iU_S(n^U(DlfXI_A$z80!v?0B+B`JmD8&YV? zdt`YMLb1MKxpVc^mFSk@Us1LcUqM^Fc00ZP_QyZ}i@)c${a-2Pp2)=0QkzjR5O{!n z!8a(wt4dcTmD-R&KD-3^u!7n$++~vO-~gIxhy}9uqtnTyHFVZksHC&J)5K)yttA4T zGS8X(gHFq834@`BPnJ^YV{{!zk9b|qiYXhp?4riWVTdw&5j6xJt_%^cDhk{Snh`e= zv+%?PSi{VrqkW*o*Ss3Lcm6BiNqCnj-jyq>eao+{__qSb?)VGxzMOoiM0ef9-&xBD zIw@xai&%KaISy|JfnD2BhFRJAu7kRiSf6wJ9w=1kI(}8;$v<7h8+qtyD@tt#?1wpK zzy09b*{P=}zxtj%O(~UtoHGH>{}eYx=FnGt?@sXTOeG#ijd;AP(R3%FmNKBY)h!|< zQ08we^H41O6k|+_}8T6iHpeXo6_-33FWq1b_;p=Nq|qbZAz!$4<=iLB001j zAg($rS#B$IV}cFwq1i+@8F=;}YqK9j{CqcLEZO v{0fW?Q`)= random() else nodeFuncs[1] - temp[i] = func(*nodes) - nodes = temp - - -def state_to_str(): - return "".join(str(int(i)) for i in nodes) - - -def simulate(initial_state): - print("press ENTER to simulate one step. press 'q' before ENTER to exit") - init(initial_state) - state = state_to_str() - while input() != "q": - update() - print(state + " -> " + state_to_str(), end="") - - -def get_table(): - for i in product((0, 1), repeat=3): - init(tuple(i)) - state = state_to_str() - update() - print(state + " -> " + state_to_str()) - - -def update(): - # return synchronous_update() - return sequential_update([0, 1, 2]) - # return probabilistic_update() - # return block_sequential_update(...) - # return asynchronous_deterministic_update(...) - - -def main(): - get_table() - # simulate([0,0,0,0]) - - -if __name__ == "__main__": - main() diff --git a/code/__pycache__/simulator.cpython-314.pyc b/code/__pycache__/simulator.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ccf907e13b3f625ede698c35b5b3ba8c7c06de63 GIT binary patch literal 22738 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 saJj$a@|JA}Ej%E*z2QlM%tpET3 literal 0 HcmV?d00001 diff --git a/code/main.py b/code/main.py new file mode 100644 index 0000000..590d22f --- /dev/null +++ b/code/main.py @@ -0,0 +1,400 @@ +from functools import partial +from types import FunctionType +from typing import Callable, Optional + +from simple_term_menu import TerminalMenu +from simulator import BooleanNetwork + + +def ShowMenu(options: list[str], title: str = "", highlight_entry: int = 0) -> int: + menu = TerminalMenu( + menu_entries=options, + title=title, + multi_select=False, + cursor_index=highlight_entry, + ) + + selectedIndex = menu.show() + + if selectedIndex is None: + selectedIndex = len(options) - 1 + + return selectedIndex # pyright: ignore[reportReturnType] + + +def Continue() -> None: + ShowMenu(options=[None, "Continue"]) + + +# 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() + + +def MainMenu() -> None: + global currentFunction + + def SelectExampleMenu() -> None: + def bn4nodeSync() -> None: + bn = ( + BooleanNetwork(4) + .UseSynchronousScheme() + .SetFunctions( + [ + lambda x1, x2, x3, x4: not x2, + lambda x1, x2, x3, x4: x1, + lambda x1, x2, x3, x4: x1 ^ x4, + lambda x1, x2, x3, x4: x3, + ] + ) + ) + global currentFunction + funcStrings = ["not x2", "x1", "x1 ^ x4", "x3"] + currentFunction = partial(BooleanNetworkMenu, bn, funcStrings) + + def bn4nodeSeq() -> None: + bn = ( + BooleanNetwork(4) + .UseSequentialScheme([1, 2, 3, 4]) + .SetFunctions( + [ + lambda x1, x2, x3, x4: not x2, + lambda x1, x2, x3, x4: x1, + lambda x1, x2, x3, x4: x1 ^ x4, + lambda x1, x2, x3, x4: x3, + ] + ) + ) + global currentFunction + funcStrings = ["not x2", "x1", "x1 ^ x4", "x3"] + currentFunction = partial(BooleanNetworkMenu, bn, funcStrings) + + def bn3nodeSync() -> None: + bn = ( + BooleanNetwork(3) + .UseSynchronousScheme() + .SetFunctions( + [ + lambda x1, x2, x3: not x3, + lambda x1, x2, x3: not x1, + lambda x1, x2, x3: not x2, + ] + ) + ) + global currentFunction + funcStrings = ["not x3", "not x1", "not x2"] + currentFunction = partial(BooleanNetworkMenu, bn, funcStrings) + + def bn3nodeSeq() -> None: + bn = ( + BooleanNetwork(3) + .UseSequentialScheme([1, 2, 3]) + .SetFunctions( + [ + lambda x1, x2, x3: not x3, + lambda x1, x2, x3: not x1, + lambda x1, x2, x3: not x2, + ] + ) + ) + global currentFunction + funcStrings = ["not x3", "not x1", "not x2"] + currentFunction = partial(BooleanNetworkMenu, bn, funcStrings) + + def ReturnHelper() -> None: + global currentFunction + currentFunction = MainMenu + + title = "Select an example boolean network:" + options = [ + "4-node network with synchronous update", + "4-node network with sequential update", + "3-node repressilator network with synchronous update", + "3-node repressilator network with sequential update", + "Return to main menu", + ] + actions = [bn4nodeSync, bn4nodeSeq, bn3nodeSync, bn3nodeSeq, ReturnHelper] + + actions[ShowMenu(options=options, title=title)]() + + options = [ + "Set up a new boolean network", + "Set up a boolean network from existing ones", + "Quit", + ] + + actions: list[Callable[[], None]] = [ + BooleanNetworkMenu, + SelectExampleMenu, + StopApplication, + ] + + selectedIndex = ShowMenu( + options, title="Boolean Network Simulator\nby Tom Zuidberg" + ) + + currentFunction = actions[selectedIndex] + + +def BooleanNetworkMenu( + bn: Optional[BooleanNetwork] = None, funcStrings: Optional[list[str]] = None +) -> None: + global currentFunction + + boolNetwork: BooleanNetwork = BooleanNetwork(3) + verbose: bool = False + done: bool = False + functions: list = [None for _ in range(3)] + functionStrings: list = [None for _ in range(3)] + functionsDirtyFlag = False + current_highlight = 0 + + if bn is not None and funcStrings is not None: + boolNetwork = bn + verbose = False + functions = [None for _ in range(3)] + functionStrings = funcStrings + + def Menu() -> None: + nonlocal current_highlight + title: str = f"Boolean Network: {boolNetwork} (timeStep | state)" + options = [ + 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) + ], + None, + f"Toggle verbose update: (current: {verbose})", + "Update once", + "Update multiple times", + "Get stationairy distribution", + None, + "Return to main menu", + ] + + actions = [ + SetSizeHelper, + SetStateHelper, + SetUpdateSchemeHelper, + *[partial(SetFunctionHelper, i) for i in range(boolNetwork.size)], + None, + ToggleVerboseHelper, + UpdateHelper, + MultiUpdateHelper, + GetStableDistrHelper, + None, + ReturnHelper, + ] + + selectedIndex = ShowMenu( + options=options, title=title, highlight_entry=current_highlight + ) + current_highlight = selectedIndex + actions[selectedIndex]() + + def SetSizeHelper() -> None: + nonlocal boolNetwork, functions, functionStrings + while True: + size = input("Set new size (Leave empty to cancel):\n").strip() + if size == "": + print("Cancelled") + return + try: + size = int(size) + except ValueError: + print("Please enter only integers.") + continue + + boolNetwork = BooleanNetwork(size) + functions = [None for _ in range(size)] + functionStrings = [None for _ in range(size)] + return + + def SetStateHelper() -> None: + nonlocal boolNetwork + while True: + state = input( + "Set new state (Leave empty to cancel).\nAccepted format example: '01001'\n" + ).strip() + if state == "": + print("Cancelled") + return + try: + boolNetwork.SetState(state) + return + except AssertionError as e: + print("Invalid input:", e) + + def SetUpdateSchemeHelper() -> None: + nonlocal boolNetwork + + def SetSequentialHelper() -> None: + while True: + seq = input( + "Set sequence. (Leave empty to cancel)\nFormat example: '1,4,3,2'\n" + ).strip() + if seq == "": + print("Cancelled") + return + + seq = seq.split(",") + try: + seq = [int(i) for i in seq] + boolNetwork.UseSequentialScheme(seq) + 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 + + try: + chance = float(chance) + boolNetwork.UseProbabilisticScheme(chance) + return + + except Exception as e: + print("Invalid input:", e) + + title = "Select update scheme:" + options = [ + "Synchronous update scheme", + "Sequential update scheme", + "Probabilistic update scheme", + "Asynchronous random update scheme", + "Cancel", + ] + actions = [ + lambda: boolNetwork.UseSynchronousScheme(), + SetSequentialHelper, + SetProbabilisticHelper, + lambda: boolNetwork.UseAsynchronousRandomScheme(), + lambda: None, + ] + + actions[ShowMenu(options=options, title=title)]() + + def UpdateHelper() -> None: + nonlocal functions, boolNetwork, functionsDirtyFlag + if functionsDirtyFlag: + try: + boolNetwork.SetFunctions(functions) + functionsDirtyFlag = False + except Exception as e: + print("Error while updating the functions:", e) + return + + try: + boolNetwork.Update() + except Exception as e: + print("Error while simulating once:", e) + + def MultiUpdateHelper() -> None: + nonlocal boolNetwork, verbose, functions, functionsDirtyFlag + if functionsDirtyFlag: + try: + boolNetwork.SetFunctions(functions) + functionsDirtyFlag = False + except Exception as e: + print("Error while updating functions:", e) + + n = 0 + while True: + n = input( + "Enter amount of time steps to be simulated. (Leave empty to cancel)\n" + ).strip() + if n == "": + print("Cancelled") + return + + try: + n = int(n) + if n < 0: + raise ValueError + break + except ValueError: + print("Invalid input") + try: + boolNetwork.Update(n, verbose=verbose) + if verbose: + Continue() + print("\n") + except Exception as e: + print("Error while simulating:", e) + + def GetStableDistrHelper() -> None: + print(boolNetwork.GetStableProbabilityDistribution()) + Continue() + print("\n") + + def SetFunctionHelper(index: int) -> None: + nonlocal boolNetwork, functions, functionStrings, functionsDirtyFlag + 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 + functions[index] = func + functionStrings[index] = funcString + functionsDirtyFlag = True + return + except Exception as e: + print("Error while parsing function:", e) + + def ToggleVerboseHelper(): + nonlocal verbose + verbose = not verbose + + def ReturnHelper(): + nonlocal done + done = True + + while not done: + Menu() + + currentFunction = MainMenu + + +def main() -> None: + global currentFunction + currentFunction = MainMenu + + while True: + currentFunction() + + +if __name__ == "__main__": + main() diff --git a/code/requirements.txt b/code/requirements.txt new file mode 100644 index 0000000..52158a0 --- /dev/null +++ b/code/requirements.txt @@ -0,0 +1,3 @@ +scipy +numpy +simple-term-menu diff --git a/code/simulator.py b/code/simulator.py new file mode 100644 index 0000000..a882027 --- /dev/null +++ b/code/simulator.py @@ -0,0 +1,299 @@ +import inspect +import random +from itertools import product +from typing import Callable, Concatenate, Iterable, Self + +import numpy as np +import scipy.linalg + + +class BooleanNetwork: + def __init__(self, size: int) -> None: + assert type(size) is int and size > 0, ( + f"Init error: Boolean Network must contain atleast one node. got {size=} nodes" + ) + self.size = size + self.__ready = False + 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.sequence: list[int] = list() + self.seed: int | None = None + self.time_step = 0 + + self.updateScheme: None | str = None + self.nodes: list[bool] = [False for _ in range(size)] + self.functions: list[Callable[Concatenate[bool, ...], bool]] = [ + lambda x: x 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, ( + f"Function error: Function amount mismatch. got {len(funcs)} functions, expected {self.size}" + ) + + for i in range(self.size): + func = funcs[i] + assert len(inspect.signature(func).parameters) == self.size, ( + 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.__has_update_functions = True + return self + + 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) + return self + + def UseSynchronousScheme(self) -> Self: + self.updateScheme = "synchronous" + self.__has_update_scheme = True + + return self + + def UseSequentialScheme(self, sequence: Iterable[int]) -> Self: + sequence = list(sequence) + assert len(sequence) == self.size, ( + f"Sequence error: sequence must be the same size as the nodes of the network: sequence '{sequence}', #nodes={self.size}" + ) + assert all(type(i) is int for i in sequence), ( + f"Sequence error: sequence must only contain integers. sequence given: {sequence}" + ) + sorted_sequence = sorted(sequence) + compare_to = list(range(self.size + 1)) + assert ( + sorted_sequence == compare_to[:-1] or sorted_sequence == compare_to[1:] + ), ( + f"Sequence error: sequence doesn't contain the correct indices. It must contain all numbers from 0 to {self.size} (excluded) or from 1 to {self.size} (included)" + ) + + if sorted_sequence[0] == 1: + for i in range(self.size): + sequence[i] -= 1 + + self.sequence = sequence + self.updateScheme = "sequential" + self.__has_update_scheme = True + self.__has_sequence = True + + return self + + def UseAsynchronousRandomScheme(self, seed: int | None = None) -> Self: + if seed is not None: + assert type(seed) is int, ( + f"AsyncRandom error: wrong format for given seed. got {seed=}" + ) + self.seed = seed + random.seed(seed) + + self.updateScheme = "asynchronous_random" + 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 + + self.updateScheme = "probabilistic" + self.__has_update_scheme = True + self.__has_flip_chance = True + return self + + def __synchronous_update(self) -> None: + temp = list() + for i in range(self.size): + temp.append(self.functions[i](*self.nodes)) + self.nodes = temp + + def __sequential_update(self) -> None: + for i in self.sequence: + self.nodes[i] = self.functions[i](*self.nodes) + + def __asynchronous_random_update(self) -> None: + index = random.randrange(0, self.size) + self.nodes[index] = self.functions[index](*self.nodes) + + def __probabilistic_update(self) -> None: + self.__synchronous_update() + for i in range(self.size): + rng = random.random() + if rng <= self.flip_chance: + self.nodes[i] = not self.nodes[i] + + def SetState(self, state: str | list[bool] | tuple[bool, ...]) -> Self: + assert isinstance(state, (str, list, tuple)), ( + f"SetState error: invalid type as state" + ) + assert len(state) == self.size, ( + f"SetState error: given state is not the same size as the boolean network. got size {len(state)}, expected {self.size}" + ) + + if type(state) is str: + for i in range(self.size): + self.nodes[i] = bool(int(state[i])) + return self + + if isinstance(state, (list, tuple)): + assert all(type(i) is bool for i in state), ( + f"SetState error: given state list contains elements of type different from bool. All elements must be bools. got {state}" + ) + self.nodes = list(state).copy() + return self + + raise Exception( + "SetState error: end of function reached. given state is not of type string nor list[bool]." + ) + + def Update(self, n: int = 1, /, verbose=False) -> None: + 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.__has_update_scheme, "Update error: no update scheme defined" + assert type(verbose) is bool, "Update error: verbose must be a bool" + + selected_update: Callable + + match self.updateScheme: + case "synchronous": + selected_update = self.__synchronous_update + case "sequential": + assert self.__has_sequence, "Update error: no sequence defined" + selected_update = self.__sequential_update + case "asynchronous_random": + selected_update = self.__asynchronous_random_update + case "probabilistic": + assert self.__has_flip_chance, "Update error: no flip_chance defined" + selected_update = self.__probabilistic_update + case _: + raise Exception("Update error: update scheme selection went wrong") + + for _ in range(n): + selected_update() + self.time_step += 1 + if verbose: + print(self) + + def __str__(self) -> str: + return f"{self.time_step:>5} | {''.join(str(int(node)) for node in self.nodes)}" + + @property + def state(self) -> str: + return "".join(str(int(node)) for node in self.nodes) + + def GetStableProbabilityDistribution(self) -> np.ndarray: + matrix: np.ndarray = self.__get_transition_matrix() + eigenvalues, eigenvectors = scipy.linalg.eig(matrix.T) + stationary_vector = np.real(eigenvectors[:, np.isclose(eigenvalues, 1)]) + stationary_distribution = stationary_vector / np.sum(stationary_vector) + return stationary_distribution.flatten() + + def __get_transition_matrix(self) -> np.ndarray: + dimension = 2**self.size + 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, + ) + prob = np.float64(1) + for flip in flips: + prob *= flipChance if flip else 1 - flipChance + matrix[i][flipped] = prob + self.UseProbabilisticScheme(flipChance) + return matrix + + if self.updateScheme == "asynchronous_random": + 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 + return matrix + + for i, state in enumerate(product((False, True), repeat=self.size)): + self.SetState(state) + self.Update() + matrix[i][int(self.state, 2)] = 1 + return matrix + + +def main() -> None: + bn = ( + BooleanNetwork(4) + .SetState("0000") + .SetFunctions( + [ + lambda a, b, c, d: not b, + lambda a, b, c, d: a, + lambda a, b, c, d: a ^ d, + lambda a, b, c, d: c, + ] + ) + .UseSequentialScheme((1, 2, 3, 4)) + ) + + print(bn) + bn.Update() + print(bn) + + print("update 100 times verbose") + bn.Update(100, verbose=True) + + +if __name__ == "__main__": + main()