from functools import partial from types import FunctionType from typing import Callable, Optional import numpy as np from simple_term_menu import TerminalMenu from simulator import BooleanNetwork from typing_extensions import Concatenate 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(title="") -> None: ShowMenu(title=title, options=[None, "Continue"], highlight_entry=1) 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() def MainMenu() -> None: global currentFunction def SelectExampleMenu() -> None: def bn4nodeSync() -> None: functions = [ 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, ] bn = BooleanNetwork(4).UseSynchronousScheme().SetFunctions(functions) global currentFunction funcStrings = ["not x2", "x1", "x1 ^ x4", "x3"] currentFunction = partial(BooleanNetworkMenu, bn, functions, funcStrings) def bn4nodeSeq() -> None: functions = [ 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, ] bn = ( BooleanNetwork(4) .UseSequentialScheme([1, 2, 3, 4]) .SetFunctions(functions) ) global currentFunction funcStrings = ["not x2", "x1", "x1 ^ x4", "x3"] currentFunction = partial(BooleanNetworkMenu, bn, functions, funcStrings) def bn3nodeSync() -> None: functions = [ lambda x1, x2, x3: not x3, lambda x1, x2, x3: not x1, lambda x1, x2, x3: not x2, ] bn = BooleanNetwork(3).UseSynchronousScheme().SetFunctions(functions) global currentFunction funcStrings = ["not x3", "not x1", "not x2"] currentFunction = partial(BooleanNetworkMenu, bn, functions, funcStrings) def bn3nodeSeq() -> None: functions = [ lambda x1, x2, x3: not x3, lambda x1, x2, x3: not x1, lambda x1, x2, x3: not x2, ] bn = ( BooleanNetwork(3).UseSequentialScheme([1, 2, 3]).SetFunctions(functions) ) global currentFunction funcStrings = ["not x3", "not x1", "not x2"] currentFunction = partial(BooleanNetworkMenu, bn, functions, 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, funcs: Optional[list[Callable[Concatenate[bool, ...], bool]]] = None, funcStrings: Optional[list[str]] = None, ) -> None: global currentFunction boolNetwork: BooleanNetwork = BooleanNetwork(3) writeToFile: 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 and funcs is not None: boolNetwork = bn functions = funcs 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, "Update once (hold ENTER for continuous updates)", "Update multiple times", f"Write to file (works only with multi-update) (current: {writeToFile})", "Get Markov-Chain matrix (resets timeStep and state)", "Get stationairy distribution (resets timeStep and state)", None, "Return to main menu", ] actions = [ SetSizeHelper, SetStateHelper, SetUpdateSchemeHelper, *[partial(SetFunctionHelper, i) for i in range(boolNetwork.size)], None, UpdateHelper, MultiUpdateHelper, ToggleWriteToFileHelper, GetMarkovChainHelper, GetStableDistrHelper, None, ReturnHelper, ] if boolNetwork.updateScheme in ("synchronous", "sequential", None): options.pop(-3) actions.pop(-3) 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) 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, writeToFile, 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=not writeToFile or n < 1000, writeToFile=writeToFile ) Continue("Writing to file completed") if writeToFile else Continue() except Exception as e: print("Error while simulating:", e) def GetMarkovChainHelper() -> None: nonlocal boolNetwork print(boolNetwork.GetTransitionMatrix()) Continue() boolNetwork.SetState([False for _ in range(boolNetwork.size)]) def GetStableDistrHelper() -> None: print(boolNetwork.GetStableProbabilityDistribution()) Continue() boolNetwork.SetState([False for _ in range(boolNetwork.size)]) 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 ToggleWriteToFileHelper(): nonlocal writeToFile writeToFile = not writeToFile def ReturnHelper(): nonlocal done done = True while not done: Menu() currentFunction = MainMenu def main() -> None: global currentFunction currentFunction = MainMenu np.set_printoptions(precision=4, linewidth=300, sign=" ") while True: currentFunction() if __name__ == "__main__": main()