628 lines
22 KiB
Python
628 lines
22 KiB
Python
from functools import partial
|
|
from types import FunctionType
|
|
from typing import Callable, Optional
|
|
|
|
import numpy as np
|
|
from simple_term_menu import TerminalMenu
|
|
from typing_extensions import Concatenate
|
|
|
|
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(title="") -> None:
|
|
ShowMenu(title=title, options=[None, "Continue"], highlight_entry=1)
|
|
print()
|
|
|
|
|
|
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
|
|
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
|
|
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})",
|
|
"Edit update functions",
|
|
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,
|
|
FunctionsMenu,
|
|
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, probFunctionStrings
|
|
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)]
|
|
probFunctionStrings = [[] 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, 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"
|
|
).strip()
|
|
if seq == "":
|
|
print("Cancelled")
|
|
return
|
|
|
|
seq = seq.split(",")
|
|
try:
|
|
seq = [int(i) for i in seq]
|
|
boolNetwork.UseSequentialScheme(seq)
|
|
if wasProbabilistic:
|
|
AdoptFromProbabilistic()
|
|
return
|
|
except Exception as e:
|
|
print("Invalid input:", e)
|
|
|
|
def SwitchToAsyncRandom() -> None:
|
|
wasProbabilistic = boolNetwork.updateScheme == "probabilistic"
|
|
boolNetwork.UseAsynchronousRandomScheme()
|
|
if wasProbabilistic:
|
|
AdoptFromProbabilistic()
|
|
|
|
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 = [
|
|
"Synchronous update scheme",
|
|
"Sequential update scheme",
|
|
"Probabilistic update scheme",
|
|
"Asynchronous random update scheme",
|
|
"Cancel",
|
|
]
|
|
actions = [
|
|
SwitchToSynchronous,
|
|
SetSequentialHelper,
|
|
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:
|
|
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 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
|
|
|
|
def ReturnHelper():
|
|
nonlocal done
|
|
done = True
|
|
|
|
while not done:
|
|
Menu()
|
|
|
|
currentFunction = MainMenu
|
|
|
|
|
|
def main() -> None:
|
|
global currentFunction
|
|
currentFunction = MainMenu
|
|
np.set_printoptions(precision=5, linewidth=300, sign=" ")
|
|
while True:
|
|
currentFunction()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|