fixed some bugs, added dockerfile for running on windows. output files must be extracted manually.
This commit is contained in:
11
code/Dockerfile
Normal file
11
code/Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
FROM python:3.14-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY ./main.py /app
|
||||||
|
COPY ./simulator.py /app
|
||||||
|
COPY ./requirements.txt /app
|
||||||
|
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
CMD ["python", "main.py"]
|
||||||
133
code/main.py
133
code/main.py
@@ -2,8 +2,10 @@ from functools import partial
|
|||||||
from types import FunctionType
|
from types import FunctionType
|
||||||
from typing import Callable, Optional
|
from typing import Callable, Optional
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
from simple_term_menu import TerminalMenu
|
from simple_term_menu import TerminalMenu
|
||||||
from simulator import BooleanNetwork
|
from simulator import BooleanNetwork
|
||||||
|
from typing_extensions import Concatenate
|
||||||
|
|
||||||
|
|
||||||
def ShowMenu(options: list[str], title: str = "", highlight_entry: int = 0) -> int:
|
def ShowMenu(options: list[str], title: str = "", highlight_entry: int = 0) -> int:
|
||||||
@@ -22,8 +24,9 @@ def ShowMenu(options: list[str], title: str = "", highlight_entry: int = 0) -> i
|
|||||||
return selectedIndex # pyright: ignore[reportReturnType]
|
return selectedIndex # pyright: ignore[reportReturnType]
|
||||||
|
|
||||||
|
|
||||||
def Continue() -> None:
|
def Continue(title="") -> None:
|
||||||
ShowMenu(options=[None, "Continue"])
|
ShowMenu(title=title, options=[None, "Continue"], highlight_entry=1)
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
# def MainMenu() -> None:
|
# def MainMenu() -> None:
|
||||||
@@ -47,70 +50,56 @@ def MainMenu() -> None:
|
|||||||
|
|
||||||
def SelectExampleMenu() -> None:
|
def SelectExampleMenu() -> None:
|
||||||
def bn4nodeSync() -> None:
|
def bn4nodeSync() -> None:
|
||||||
bn = (
|
functions = [
|
||||||
BooleanNetwork(4)
|
lambda x1, x2, x3, x4: not x2,
|
||||||
.UseSynchronousScheme()
|
lambda x1, x2, x3, x4: x1,
|
||||||
.SetFunctions(
|
lambda x1, x2, x3, x4: x1 ^ x4,
|
||||||
[
|
lambda x1, x2, x3, x4: x3,
|
||||||
lambda x1, x2, x3, x4: not x2,
|
]
|
||||||
lambda x1, x2, x3, x4: x1,
|
bn = BooleanNetwork(4).UseSynchronousScheme().SetFunctions(functions)
|
||||||
lambda x1, x2, x3, x4: x1 ^ x4,
|
|
||||||
lambda x1, x2, x3, x4: x3,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
global currentFunction
|
global currentFunction
|
||||||
funcStrings = ["not x2", "x1", "x1 ^ x4", "x3"]
|
funcStrings = ["not x2", "x1", "x1 ^ x4", "x3"]
|
||||||
currentFunction = partial(BooleanNetworkMenu, bn, funcStrings)
|
currentFunction = partial(BooleanNetworkMenu, bn, functions, funcStrings)
|
||||||
|
|
||||||
def bn4nodeSeq() -> None:
|
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 = (
|
bn = (
|
||||||
BooleanNetwork(4)
|
BooleanNetwork(4)
|
||||||
.UseSequentialScheme([1, 2, 3, 4])
|
.UseSequentialScheme([1, 2, 3, 4])
|
||||||
.SetFunctions(
|
.SetFunctions(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,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
global currentFunction
|
global currentFunction
|
||||||
funcStrings = ["not x2", "x1", "x1 ^ x4", "x3"]
|
funcStrings = ["not x2", "x1", "x1 ^ x4", "x3"]
|
||||||
currentFunction = partial(BooleanNetworkMenu, bn, funcStrings)
|
currentFunction = partial(BooleanNetworkMenu, bn, functions, funcStrings)
|
||||||
|
|
||||||
def bn3nodeSync() -> None:
|
def bn3nodeSync() -> None:
|
||||||
bn = (
|
functions = [
|
||||||
BooleanNetwork(3)
|
lambda x1, x2, x3: not x3,
|
||||||
.UseSynchronousScheme()
|
lambda x1, x2, x3: not x1,
|
||||||
.SetFunctions(
|
lambda x1, x2, x3: not x2,
|
||||||
[
|
]
|
||||||
lambda x1, x2, x3: not x3,
|
bn = BooleanNetwork(3).UseSynchronousScheme().SetFunctions(functions)
|
||||||
lambda x1, x2, x3: not x1,
|
|
||||||
lambda x1, x2, x3: not x2,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
global currentFunction
|
global currentFunction
|
||||||
funcStrings = ["not x3", "not x1", "not x2"]
|
funcStrings = ["not x3", "not x1", "not x2"]
|
||||||
currentFunction = partial(BooleanNetworkMenu, bn, funcStrings)
|
currentFunction = partial(BooleanNetworkMenu, bn, functions, funcStrings)
|
||||||
|
|
||||||
def bn3nodeSeq() -> None:
|
def bn3nodeSeq() -> None:
|
||||||
|
functions = [
|
||||||
|
lambda x1, x2, x3: not x3,
|
||||||
|
lambda x1, x2, x3: not x1,
|
||||||
|
lambda x1, x2, x3: not x2,
|
||||||
|
]
|
||||||
bn = (
|
bn = (
|
||||||
BooleanNetwork(3)
|
BooleanNetwork(3).UseSequentialScheme([1, 2, 3]).SetFunctions(functions)
|
||||||
.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
|
global currentFunction
|
||||||
funcStrings = ["not x3", "not x1", "not x2"]
|
funcStrings = ["not x3", "not x1", "not x2"]
|
||||||
currentFunction = partial(BooleanNetworkMenu, bn, funcStrings)
|
currentFunction = partial(BooleanNetworkMenu, bn, functions, funcStrings)
|
||||||
|
|
||||||
def ReturnHelper() -> None:
|
def ReturnHelper() -> None:
|
||||||
global currentFunction
|
global currentFunction
|
||||||
@@ -148,22 +137,23 @@ def MainMenu() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def BooleanNetworkMenu(
|
def BooleanNetworkMenu(
|
||||||
bn: Optional[BooleanNetwork] = None, funcStrings: Optional[list[str]] = None
|
bn: Optional[BooleanNetwork] = None,
|
||||||
|
funcs: Optional[list[Callable[Concatenate[bool, ...], bool]]] = None,
|
||||||
|
funcStrings: Optional[list[str]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
global currentFunction
|
global currentFunction
|
||||||
|
|
||||||
boolNetwork: BooleanNetwork = BooleanNetwork(3)
|
boolNetwork: BooleanNetwork = BooleanNetwork(3)
|
||||||
verbose: bool = False
|
writeToFile: bool = False
|
||||||
done: bool = False
|
done: bool = False
|
||||||
functions: list = [None for _ in range(3)]
|
functions: list = [None for _ in range(3)]
|
||||||
functionStrings: list = [None for _ in range(3)]
|
functionStrings: list = [None for _ in range(3)]
|
||||||
functionsDirtyFlag = False
|
functionsDirtyFlag = False
|
||||||
current_highlight = 0
|
current_highlight = 0
|
||||||
|
|
||||||
if bn is not None and funcStrings is not None:
|
if bn is not None and funcStrings is not None and funcs is not None:
|
||||||
boolNetwork = bn
|
boolNetwork = bn
|
||||||
verbose = False
|
functions = funcs
|
||||||
functions = [None for _ in range(3)]
|
|
||||||
functionStrings = funcStrings
|
functionStrings = funcStrings
|
||||||
|
|
||||||
def Menu() -> None:
|
def Menu() -> None:
|
||||||
@@ -178,10 +168,11 @@ def BooleanNetworkMenu(
|
|||||||
for i in range(1, boolNetwork.size + 1)
|
for i in range(1, boolNetwork.size + 1)
|
||||||
],
|
],
|
||||||
None,
|
None,
|
||||||
f"Toggle verbose update: (current: {verbose})",
|
"Update once (hold ENTER for continuous updates)",
|
||||||
"Update once",
|
|
||||||
"Update multiple times",
|
"Update multiple times",
|
||||||
"Get stationairy distribution",
|
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,
|
None,
|
||||||
"Return to main menu",
|
"Return to main menu",
|
||||||
]
|
]
|
||||||
@@ -192,14 +183,19 @@ def BooleanNetworkMenu(
|
|||||||
SetUpdateSchemeHelper,
|
SetUpdateSchemeHelper,
|
||||||
*[partial(SetFunctionHelper, i) for i in range(boolNetwork.size)],
|
*[partial(SetFunctionHelper, i) for i in range(boolNetwork.size)],
|
||||||
None,
|
None,
|
||||||
ToggleVerboseHelper,
|
|
||||||
UpdateHelper,
|
UpdateHelper,
|
||||||
MultiUpdateHelper,
|
MultiUpdateHelper,
|
||||||
|
ToggleWriteToFileHelper,
|
||||||
|
GetMarkovChainHelper,
|
||||||
GetStableDistrHelper,
|
GetStableDistrHelper,
|
||||||
None,
|
None,
|
||||||
ReturnHelper,
|
ReturnHelper,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if boolNetwork.updateScheme in ("synchronous", "sequential", None):
|
||||||
|
options.pop(-3)
|
||||||
|
actions.pop(-3)
|
||||||
|
|
||||||
selectedIndex = ShowMenu(
|
selectedIndex = ShowMenu(
|
||||||
options=options, title=title, highlight_entry=current_highlight
|
options=options, title=title, highlight_entry=current_highlight
|
||||||
)
|
)
|
||||||
@@ -235,7 +231,6 @@ def BooleanNetworkMenu(
|
|||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
boolNetwork.SetState(state)
|
boolNetwork.SetState(state)
|
||||||
return
|
|
||||||
except AssertionError as e:
|
except AssertionError as e:
|
||||||
print("Invalid input:", e)
|
print("Invalid input:", e)
|
||||||
|
|
||||||
@@ -310,7 +305,7 @@ def BooleanNetworkMenu(
|
|||||||
print("Error while simulating once:", e)
|
print("Error while simulating once:", e)
|
||||||
|
|
||||||
def MultiUpdateHelper() -> None:
|
def MultiUpdateHelper() -> None:
|
||||||
nonlocal boolNetwork, verbose, functions, functionsDirtyFlag
|
nonlocal boolNetwork, writeToFile, functions, functionsDirtyFlag
|
||||||
if functionsDirtyFlag:
|
if functionsDirtyFlag:
|
||||||
try:
|
try:
|
||||||
boolNetwork.SetFunctions(functions)
|
boolNetwork.SetFunctions(functions)
|
||||||
@@ -335,17 +330,23 @@ def BooleanNetworkMenu(
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
print("Invalid input")
|
print("Invalid input")
|
||||||
try:
|
try:
|
||||||
boolNetwork.Update(n, verbose=verbose)
|
boolNetwork.Update(
|
||||||
if verbose:
|
n, verbose=not writeToFile or n < 1000, writeToFile=writeToFile
|
||||||
Continue()
|
)
|
||||||
print("\n")
|
Continue("Writing to file completed") if writeToFile else Continue()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Error while simulating:", 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:
|
def GetStableDistrHelper() -> None:
|
||||||
print(boolNetwork.GetStableProbabilityDistribution())
|
print(boolNetwork.GetStableProbabilityDistribution())
|
||||||
Continue()
|
Continue()
|
||||||
print("\n")
|
boolNetwork.SetState([False for _ in range(boolNetwork.size)])
|
||||||
|
|
||||||
def SetFunctionHelper(index: int) -> None:
|
def SetFunctionHelper(index: int) -> None:
|
||||||
nonlocal boolNetwork, functions, functionStrings, functionsDirtyFlag
|
nonlocal boolNetwork, functions, functionStrings, functionsDirtyFlag
|
||||||
@@ -374,9 +375,9 @@ def BooleanNetworkMenu(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Error while parsing function:", e)
|
print("Error while parsing function:", e)
|
||||||
|
|
||||||
def ToggleVerboseHelper():
|
def ToggleWriteToFileHelper():
|
||||||
nonlocal verbose
|
nonlocal writeToFile
|
||||||
verbose = not verbose
|
writeToFile = not writeToFile
|
||||||
|
|
||||||
def ReturnHelper():
|
def ReturnHelper():
|
||||||
nonlocal done
|
nonlocal done
|
||||||
@@ -391,7 +392,7 @@ def BooleanNetworkMenu(
|
|||||||
def main() -> None:
|
def main() -> None:
|
||||||
global currentFunction
|
global currentFunction
|
||||||
currentFunction = MainMenu
|
currentFunction = MainMenu
|
||||||
|
np.set_printoptions(precision=4, linewidth=300, sign=" ")
|
||||||
while True:
|
while True:
|
||||||
currentFunction()
|
currentFunction()
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
scipy
|
scipy
|
||||||
numpy
|
numpy
|
||||||
simple-term-menu
|
simple-term-menu
|
||||||
|
typing_extensions
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ class BooleanNetwork:
|
|||||||
if type(state) is str:
|
if type(state) is str:
|
||||||
for i in range(self.size):
|
for i in range(self.size):
|
||||||
self.nodes[i] = bool(int(state[i]))
|
self.nodes[i] = bool(int(state[i]))
|
||||||
|
self.time_step = 0
|
||||||
return self
|
return self
|
||||||
|
|
||||||
if isinstance(state, (list, tuple)):
|
if isinstance(state, (list, tuple)):
|
||||||
@@ -178,19 +179,23 @@ class BooleanNetwork:
|
|||||||
f"SetState error: given state list contains elements of type different from bool. All elements must be bools. got {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()
|
self.nodes = list(state).copy()
|
||||||
|
self.time_step = 0
|
||||||
return self
|
return self
|
||||||
|
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"SetState error: end of function reached. given state is not of type string nor list[bool]."
|
"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:
|
def Update(
|
||||||
|
self, n: int = 1, /, verbose: bool = False, writeToFile: bool = False
|
||||||
|
) -> None:
|
||||||
assert type(n) is int and n >= 0, (
|
assert type(n) is int and n >= 0, (
|
||||||
f"Update error: amount of updates must be an integer and positive. got {n=}"
|
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_functions, "Update error: no update functions defined"
|
||||||
assert self.__has_update_scheme, "Update error: no update scheme 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(verbose) is bool, "Update error: verbose must be a bool"
|
||||||
|
assert type(writeToFile) is bool, "Update error: writeToFile must be a bool"
|
||||||
|
|
||||||
selected_update: Callable
|
selected_update: Callable
|
||||||
|
|
||||||
@@ -208,11 +213,29 @@ class BooleanNetwork:
|
|||||||
case _:
|
case _:
|
||||||
raise Exception("Update error: update scheme selection went wrong")
|
raise Exception("Update error: update scheme selection went wrong")
|
||||||
|
|
||||||
for _ in range(n):
|
match (verbose, writeToFile):
|
||||||
selected_update()
|
case (False, False):
|
||||||
self.time_step += 1
|
for _ in range(n):
|
||||||
if verbose:
|
selected_update()
|
||||||
print(self)
|
self.time_step += 1
|
||||||
|
case (False, True):
|
||||||
|
with open("output.txt", "w") as f:
|
||||||
|
for _ in range(n):
|
||||||
|
selected_update()
|
||||||
|
self.time_step += 1
|
||||||
|
f.writelines([self.state, "\n"])
|
||||||
|
case (True, False):
|
||||||
|
for _ in range(n):
|
||||||
|
selected_update()
|
||||||
|
self.time_step += 1
|
||||||
|
print(self)
|
||||||
|
case (True, True):
|
||||||
|
with open("output.txt", "w") as f:
|
||||||
|
for _ in range(n):
|
||||||
|
selected_update()
|
||||||
|
self.time_step += 1
|
||||||
|
f.writelines([self.state, "\n"])
|
||||||
|
print(self)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"{self.time_step:>5} | {''.join(str(int(node)) for node in self.nodes)}"
|
return f"{self.time_step:>5} | {''.join(str(int(node)) for node in self.nodes)}"
|
||||||
@@ -222,13 +245,18 @@ class BooleanNetwork:
|
|||||||
return "".join(str(int(node)) for node in self.nodes)
|
return "".join(str(int(node)) for node in self.nodes)
|
||||||
|
|
||||||
def GetStableProbabilityDistribution(self) -> np.ndarray:
|
def GetStableProbabilityDistribution(self) -> np.ndarray:
|
||||||
matrix: np.ndarray = self.__get_transition_matrix()
|
matrix: np.ndarray = self.GetTransitionMatrix()
|
||||||
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:
|
eigenvalues, eigenvectors = scipy.linalg.eig(matrix.T)
|
||||||
|
|
||||||
|
idx = np.argmin(np.abs(eigenvalues - 1.0))
|
||||||
|
pi = eigenvectors[:, idx].real
|
||||||
|
|
||||||
|
pi = pi / pi.sum()
|
||||||
|
|
||||||
|
return pi
|
||||||
|
|
||||||
|
def GetTransitionMatrix(self) -> np.ndarray:
|
||||||
dimension = 2**self.size
|
dimension = 2**self.size
|
||||||
matrix: np.ndarray = np.zeros((dimension, dimension))
|
matrix: np.ndarray = np.zeros((dimension, dimension))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user