Files
reactics/examples/bdd/generators/gen_drs.py
Artur Meski f0019619b3 Refactor + tests (#6)
- Minor codebase clean-up
- Slight reorganisation of examples + tests so that we don't break stuff by accident
- CI tests
2026-04-10 18:16:49 +01:00

302 lines
8.8 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Generator for the DRS (Distributed Reaction Systems) epistemic benchmark.
Produces a distributed reaction system with epistemic properties
(knowledge operators). The system models signal transduction cascades
with configurable depth (x), number of components (y), threshold (z),
and context automaton variant (a or b).
"""
import sys
import argparse
import itertools
class SingleReaction:
def __init__(self, reactants=None, inhibitors=None, products=None):
self.reactants = set(reactants) if reactants is not None else set()
self.inhibitors = set(inhibitors) if inhibitors is not None else set()
self.products = set(products) if products is not None else set()
def get_reactants(self):
return "{" + ", ".join(self.reactants) + "}"
def get_inhibitors(self):
return "{" + ", ".join(self.inhibitors) + "}"
def get_products(self):
return "{" + ", ".join(self.products) + "}"
def __str__(self):
return (
"{"
+ f"{self.get_reactants()}, {self.get_inhibitors()} -> {self.get_products()}"
+ "};"
)
class Reactions:
def __init__(self):
self.reactions = []
def add(self, reaction):
self.reactions.append(reaction)
def __str__(self):
ret = ""
for r in self.reactions:
ret += f"{r}\n"
return ret
class Transition:
def __init__(self, src, dst, guard, context):
self.src = src
self.dst = dst
self.guard = guard
self.context = context
def get_context_str(self):
r = []
for proc, entities in self.context:
ent_str = ",".join(entities)
r.append(f"{proc}={{{ent_str}}}")
r = " ".join(r)
return "{ " + r + " }"
def __str__(self):
if self.guard:
return f"{self.get_context_str()}: {self.src} -> {self.dst} : {self.guard};"
else:
return f"{self.get_context_str()}: {self.src} -> {self.dst};"
class Automaton:
def __init__(self):
self.transitions = []
self.states = []
self.init_state = None
def add_transition(self, transition):
self.transitions.append(transition)
def add_state(self, state):
if state not in self.states:
self.states.append(state)
def set_init_state(self, state):
assert state in self.states, f"{state} not in states"
self.init_state = state
def __str__(self):
r = "context-automaton {\n"
r += "\tstates { " + ", ".join(self.states) + " };\n"
r += f"\tinit-state {{ {self.init_state} }};\n"
r += "\ttransitions {\n"
for tr in self.transitions:
r += "\t\t" + str(tr) + "\n"
r += "\t};\n"
r += "};\n"
return r
class DRSGenerator:
def __init__(self, x, y, z, aut):
assert y >= z
assert x >= 2 and y >= 2 and z >= 2
self.x = x
self.y = y
self.z = z
self.aut_id = aut
self.n = self.y
self.reactions = {}
self.automaton = Automaton()
self.generate()
def generate_agent_zero(self):
rcts = Reactions()
for i in range(1, self.y + 1):
rcts.add(SingleReaction(["RTK"], ["h"], [f"RTK{i}"]))
for j in range(1, self.x + 1):
rcts.add(SingleReaction([f"EN:{j}_{i}"], ["h"], [f"ENi:{self.x}_{i}"]))
return rcts
def generate_reactions_for_component(self, i):
self.reactions.setdefault(i, Reactions())
rcts = self.reactions[i]
rcts.add(SingleReaction(["GF"], ["h"], ["GF"]))
rcts.add(SingleReaction(["GF"], ["h", f"RTK{i}"], ["RTK"]))
rcts.add(SingleReaction(["RTK"], [f"ENi:1_{i}"], [f"EN:1_{i}"]))
for j in range(1, self.x):
rcts.add(
SingleReaction([f"EN:{j}_{i}"], [f"ENi:{j+1}_{i}"], [f"EN:{j+1}_{i}"])
)
indices = list(range(1, self.y + 1))
for comb in itertools.combinations(indices, self.z):
reactants = []
for i in comb:
reactants.append(f"EN:{self.x}_{i}")
rcts.add(SingleReaction(reactants, ["h"], ["TF"]))
def generate(self):
print(f"# Generated for: x = {self.x}, y = {self.y}, z = {self.z}")
print("options { use-context-automaton; make-progressive; };")
print("reactions {")
for i in range(1, self.y + 1):
self.generate_reactions_for_component(i)
rcts_0 = self.generate_agent_zero()
print("proc0 {")
print(rcts_0)
print("};")
for proc, reactions in self.reactions.items():
print(f"proc{proc} {{")
print(reactions)
print("};")
print("};")
if self.aut_id == "a":
self.generate_automaton_4(self.n)
elif self.aut_id == "b":
self.generate_automaton_5(self.n)
else:
raise RuntimeError(f"Unknown automaton identifier: {self.aut_id}")
self.generate_formula()
def generate_automaton_4(self, n):
aut = self.automaton
aut.add_state("qI")
aut.add_state("qS")
for i in range(1, n + 1):
aut.add_state(f"q{i}A")
aut.add_state(f"q{i}B")
aut.add_state(f"q{i}C")
aut.add_state(f"q{i}D")
aut.set_init_state("qI")
aut.add_transition(
Transition("qI", "qS", "", [(f"proc{i}", ["GF"]) for i in range(0, n + 1)])
)
for i in range(1, n + 1):
aut.add_transition(Transition("qS", f"q{i}A", "", [(f"proc{i}", [])]))
aut.add_transition(
Transition(
f"q{i}A", f"q{i}B", "", [(f"proc{v}", []) for v in range(0, n + 1)]
)
)
aut.add_transition(Transition(f"q{i}B", f"q{i}C", "", [(f"proc{i}", [])]))
aut.add_transition(
Transition(
f"q{i}C", f"q{i}D", "", [(f"proc{v}", []) for v in range(0, n + 1)]
)
)
for i in range(1, n + 1):
for j in range(1, n + 1):
if i == j:
continue
aut.add_transition(
Transition(f"q{i}B", f"q{j}A", "", [(f"proc{j}", [])])
)
aut.add_transition(
Transition(f"q{i}D", f"q{j}A", "", [(f"proc{j}", [])])
)
print(aut)
def generate_automaton_5(self, n):
aut = self.automaton
for i in range(0, (4 * n + 1)):
aut.add_state(f"q{i}")
aut.set_init_state("q0")
aut.add_transition(
Transition("q0", "q1", "", [(f"proc{i}", ["GF"]) for i in range(0, n + 1)])
)
for i in range(0, n): # 0, ..., n-1
aut.add_transition(
Transition(f"q{4*i+1}", f"q{4*i+2}", "", [(f"proc{i+1}", [])])
)
aut.add_transition(
Transition(
f"q{4*i+2}",
f"q{4*i+3}",
"",
[(f"proc{v}", []) for v in range(0, n + 1)],
)
)
aut.add_transition(
Transition(f"q{4*i+3}", f"q{4*i+4}", "", [(f"proc{i+1}", [])])
)
for i in range(0, n - 1): # 0, ..., n-2
aut.add_transition(
Transition(
f"q{4*i+4}",
f"q{4*i+5}",
"",
[(f"proc{v}", []) for v in range(0, n + 1)],
)
)
aut.add_transition(
Transition(f"q{4*n}", "q1", "", [(f"proc{i}", []) for i in range(0, n + 1)])
)
print(aut)
def generate_formula(self):
disjunction = " OR ".join([f"proc{i}.TF" for i in range(1, self.y + 1)])
conjunction = " AND ".join([f"~proc{i}.TF" for i in range(1, self.y + 1)])
r = (
"rsctlk-property { f0 : AG( K[proc0]( "
+ disjunction
+ " ) OR K[proc0]( "
+ conjunction
+ " ) ) };"
)
print(r)
def main():
parser = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("x", type=int, help="cascade depth (>= 2)")
parser.add_argument("y", type=int, help="number of components (>= 2, >= z)")
parser.add_argument("z", type=int, help="threshold (>= 2, <= y)")
parser.add_argument("aut", choices=["a", "b"], help="automaton variant")
args = parser.parse_args()
if args.x < 2 or args.y < 2 or args.z < 2:
parser.error("x, y, z must all be >= 2")
if args.y < args.z:
parser.error("y must be >= z")
DRSGenerator(x=args.x, y=args.y, z=args.z, aut=args.aut)
if __name__ == "__main__":
main()