Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions cana/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,17 @@ def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):


def output_transitions(eval_line, input_list):
"""Returns an output list from combinatorically trying all input values
"""Returns an output list from combinatorically trying all input values.

Each input variable is assigned every possible binary combination (0/1)
via a namespace dict, and the boolean expression is evaluated with
``eval()`` (builtins disabled, but this is not a security boundary).
Expressions must be trusted boolean rules using ``and``, ``or``,
``not``, and parentheses, as produced by CANA model files.

Args:
eval_line (string) : logic or arithmetic line to evaluate
input_list (list) : list of input variables
eval_line (string) : boolean expression to evaluate (e.g. "A and not B")
input_list (list) : list of input variable names

Returns:
list of all possible output transitions (list)
Expand All @@ -168,19 +174,25 @@ def output_transitions(eval_line, input_list):
110
111

A variable is dynamically created for each member of the input list
and assigned the corresponding value from each trail string.
The original eval_line is then evaluated with each assignment
Each input variable is assigned the corresponding value from each
trial string via a namespace dict, and the expression is evaluated
which results in the output list [0, 0, 1, 0, 1, 0, 1, 0]
"""
total = 2 ** len(input_list) # Total combinations to try
output_list = []
# Use an explicit namespace dict for exec/eval so that dynamically
# created variables are visible across calls. In Python 3.13+
# (PEP 667) bare exec() inside a function writes to a snapshot of
# locals that eval() cannot see.
ns = {}
safe_globals = {"__builtins__": {}}
code = compile(eval_line.strip(), "<string>", "eval")
for i in range(total):
Comment thread
fxcosta-phd marked this conversation as resolved.
trial_string = statenum_to_binstate(i, len(input_list))
# Evaluate trial_string by assigning value to each input variable
for j, input in enumerate(input_list):
exec(input + "=" + trial_string[j])
output_list.append(int(eval(eval_line)))
for j, input_name in enumerate(input_list):
ns[input_name] = int(trial_string[j])
output_list.append(int(eval(code, safe_globals, ns)))

return output_list

Expand Down
38 changes: 37 additions & 1 deletion tests/test_boolean_network.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from cana.boolean_network import BooleanNetwork
import networkx as nx
from cana.datasets.bio import THALIANA
from cana.datasets.bio import THALIANA, LEUKEMIA

def test_EG_weight_THALIANA():
"""Test that effective graph in-degree edge weights are computed correctly."""
Expand All @@ -13,3 +13,39 @@ def test_EG_weight_THALIANA():
edgews = {edge: network._eg.edges[edge]["weight"] for edge in network._eg.edges if edge[1]==i}
true.append(sum(edgews.values()))
assert network.effective_indegrees() == sorted(true, reverse=True)


def test_output_transitions():
"""Test output_transitions produces correct truth tables."""
from cana.utils import output_transitions

assert output_transitions('A', ['A']) == [0, 1]
assert output_transitions('A and B', ['A', 'B']) == [0, 0, 0, 1]
assert output_transitions('A or B', ['A', 'B']) == [0, 1, 1, 1]
assert output_transitions('(A or B) and not C', ['A', 'B', 'C']) == [0, 0, 1, 0, 1, 0, 1, 0]


def test_from_string_boolean():
"""Test that BooleanNetwork.from_string_boolean parses logical rules."""
rules = "\n".join([
"A *= B",
"B *= A and C",
"C *= A or B",
])
network = BooleanNetwork.from_string_boolean(rules, keep_constants=True)
assert network.Nnodes == 3
# A = copy of B: output transitions [0, 1]
assert network.nodes[0].outputs == ['0', '1']
# B = A AND C: output transitions [0, 0, 0, 1]
assert network.nodes[1].outputs == ['0', '0', '0', '1']
# C = A OR B: output transitions [0, 1, 1, 1]
assert network.nodes[2].outputs == ['0', '1', '1', '1']


def test_load_logical_LEUKEMIA():
"""Test loading a real model in 'logical' boolean-rule format."""
network = LEUKEMIA()
assert network.Nnodes == 60
# Spot-check first rule: CTLA4* = TCR (identity)
assert network.nodes[0].name == 'CTLA4'
assert network.nodes[0].outputs == ['0', '1']
Loading