From c9efb1f29ee19f45f22a1634897b2dce076c85f5 Mon Sep 17 00:00:00 2001 From: Artur Meski Date: Tue, 18 Feb 2025 19:21:25 +0000 Subject: [PATCH 01/12] Quick add: states of CA --- reactics-bdd/mc.cc | 6 +++--- reactics-bdd/symrs.cc | 40 ++++++++++++++++++++++++++++++++++++++++ reactics-bdd/symrs.hh | 2 ++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/reactics-bdd/mc.cc b/reactics-bdd/mc.cc index 88f5e36..d1c7b4c 100644 --- a/reactics-bdd/mc.cc +++ b/reactics-bdd/mc.cc @@ -200,13 +200,13 @@ void ModelChecker::printReachWithSucc(void) t = unproc.PickOneMinterm(*pv); if (opts->backend_mode) { - cout << "G " << srs->decodedRctSysStateToStr(t) << endl; + cout << "G " << srs->decodedRctSysStateWithCtxAutToStr(t) << endl; } else { - cout << "Successors of " << srs->decodedRctSysStateToStr(t) << ":" << endl; + cout << "Successors of " << srs->decodedRctSysStateWithCtxAutToStr(t) << ":" << endl; } - srs->printDecodedRctSysStates(getSucc(t)); + srs->printDecodedRctSysWithCtxAutStates(getSucc(t)); unproc -= t; } diff --git a/reactics-bdd/symrs.cc b/reactics-bdd/symrs.cc index 98726e3..d99b38a 100644 --- a/reactics-bdd/symrs.cc +++ b/reactics-bdd/symrs.cc @@ -567,6 +567,21 @@ std::string SymRS::decodedRctSysStateToStr(const BDD &state) return s; } +std::string SymRS::decodedRctSysStateWithCtxAutToStr(const BDD &state) +{ + std::string s = "( "; + s += decodedRctSysStateToStr(state) + " "; + for (const auto &aut_state : rs->ctx_aut->states_ids) { + const auto state_id = rs->ctx_aut->getStateID(aut_state); + if (!(encCtxAutState(state_id) * state).IsZero()) { + s += aut_state + " "; + break; + } + } + s += ")"; + return s; +} + void SymRS::printDecodedRctSysStates(const BDD &states) { BDD unproc = states; @@ -591,6 +606,31 @@ void SymRS::printDecodedRctSysStates(const BDD &states) } } +void SymRS::printDecodedRctSysWithCtxAutStates(const BDD &states) +{ + BDD unproc = states; + + while (!unproc.IsZero()) { + BDD t = unproc.PickOneMinterm(*pv); + if (opts->backend_mode) + { + cout << "s " << decodedRctSysStateWithCtxAutToStr(t) << endl; + } + else + { + cout << decodedRctSysStateWithCtxAutToStr(t) << endl; + } + + if (opts->verbose > 9) { + BDD_PRINT(t); + cout << endl; + } + + unproc -= t; + } +} + + DecompReactions SymRS::getProductionConditions(Process proc_id) { DecompReactions dr; diff --git a/reactics-bdd/symrs.hh b/reactics-bdd/symrs.hh index 669910e..bcb5999 100644 --- a/reactics-bdd/symrs.hh +++ b/reactics-bdd/symrs.hh @@ -319,7 +319,9 @@ class SymRS BDD compContext(const BDD &context) const; std::string decodedRctSysStateToStr(const BDD &state); + std::string decodedRctSysStateWithCtxAutToStr(const BDD &state); void printDecodedRctSysStates(const BDD &states); + void printDecodedRctSysWithCtxAutStates(const BDD &states); DecompReactions getProductionConditions(Process proc_id); -- 2.53.0 From 063a703a8af51b38ceee9a9bbf01df0b893ae20a Mon Sep 17 00:00:00 2001 From: Artur Meski Date: Tue, 18 Feb 2025 20:43:20 +0000 Subject: [PATCH 02/12] Removed _config.yml --- _config.yml | 1 - 1 file changed, 1 deletion(-) delete mode 100644 _config.yml diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 2f7efbe..0000000 --- a/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-minimal \ No newline at end of file -- 2.53.0 From 00cd7181478ecfae1d7ebeba4e0b49a74731a80c Mon Sep 17 00:00:00 2001 From: Artur Meski Date: Tue, 18 Feb 2025 20:44:50 +0000 Subject: [PATCH 03/12] Revert "Removed _config.yml" This reverts commit 063a703a8af51b38ceee9a9bbf01df0b893ae20a. --- _config.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 _config.yml diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..2f7efbe --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-minimal \ No newline at end of file -- 2.53.0 From a86432988313bca845f0dc53b07aeca11717002e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Pi=C4=85tkowski?= Date: Wed, 2 Apr 2025 21:56:25 +0200 Subject: [PATCH 04/12] Translation into ISPL (for benchmarks with MCMAS) --- reactics-bdd/Makefile | 2 +- reactics-bdd/ctx_aut.hh | 1 + reactics-bdd/export.cc | 810 ++++++++++++++++++++++++++++++++++++ reactics-bdd/export.hh | 55 +++ reactics-bdd/formrsctlk.hh | 3 + reactics-bdd/reactics.cc | 20 +- reactics-bdd/reactics.hh | 1 + reactics-bdd/rs.hh | 1 + reactics-bdd/stateconstr.hh | 3 + 9 files changed, 891 insertions(+), 5 deletions(-) create mode 100644 reactics-bdd/export.cc create mode 100644 reactics-bdd/export.hh diff --git a/reactics-bdd/Makefile b/reactics-bdd/Makefile index 4235476..0a6c37e 100644 --- a/reactics-bdd/Makefile +++ b/reactics-bdd/Makefile @@ -11,7 +11,7 @@ CXXFLAGS = -std=c++14 -O3 -DPUBLIC_RELEASE -DNDEBUG #-g CXXFLAGS = -std=c++14 -O3 -DPUBLIC_RELEASE #-g LDLIBS = $(CUDD_INCLUDE) -OBJ = rs.o ctx_aut.o symrs.o mc.o rsin_driver.o rsin_parser.o rsin_parser.lex.o formrsctlk.o stateconstr.o +OBJ = rs.o ctx_aut.o symrs.o mc.o rsin_driver.o rsin_parser.o rsin_parser.lex.o formrsctlk.o stateconstr.o export.o all: reactics diff --git a/reactics-bdd/ctx_aut.hh b/reactics-bdd/ctx_aut.hh index 164d4c7..d446459 100644 --- a/reactics-bdd/ctx_aut.hh +++ b/reactics-bdd/ctx_aut.hh @@ -27,6 +27,7 @@ class StateConstr; class CtxAut { friend class SymRS; + friend class RSExporter; public: CtxAut(Options *opts, RctSys *parent_rctsys); diff --git a/reactics-bdd/export.cc b/reactics-bdd/export.cc new file mode 100644 index 0000000..6682cec --- /dev/null +++ b/reactics-bdd/export.cc @@ -0,0 +1,810 @@ + +#include "export.hh" + + +RSExporter::RSExporter(RctSys *rs, rsin_driver *drv, std::ostream & outStream): + rs(rs), drv(drv), output(outStream) { + + // Store all possible inputs (reactants and inhibitors) and outputs (products) for each process + + for (Process proc=0; procgetNumberOfProcesses(); ++proc) { + for (const auto & rr : rs->proc_reactions[proc]) { + for (const auto & e : rr.rctt) + procInputs[proc].insert(e); + + for (const auto & e : rr.inhib) + procInputs[proc].insert(e); + + for (const auto & e : rr.prod) + procOutputs[proc].insert(e); + } + } + + // Add possible inputs from context automaton + + for (const auto & trans : rs->ctx_aut->transitions) { + for (const auto & procCtx : trans.ctx) { + for (const auto & e : procCtx.second) { + procInputs[procCtx.first].insert(e); + } + } + } +} + + +void RSExporter::exportToISPL() { + exportEnvironment(); + + for (Process proc=0; procgetNumberOfProcesses(); ++proc) + exportAgent(proc); + + parseFormulas(); + exportEvaluation(); + exportInitStates(); + exportFormulae(); +} + + +void RSExporter::exportEnvironment() { + output << "Agent Environment\n\n"; + + exportEnvironmentVars(); + exportEnvironmentActions(); + exportEnvironmentProtocol(); + exportEnvironmentEvolution(); + + output << "\nend Agent\n" << endl; +} + + +void RSExporter::exportEnvironmentVars() { + output << "Obsvars:\n" + << indent << "mode: {init, clear, select_active_agents, activate_agents,\n" + << indent << indent << "distribute_local, add_context, distribute_global, produce,\n"; + + for (Process proc=0; procgetNumberOfProcesses(); ++proc) { + for (const Entity & e : procOutputs[proc]) { + output << indent << indent << "collect_" << rs->processes_ids[proc] + << "_" << rs->entities_ids[e] << ",\n"; + } + } + + output << indent << indent << "finalize\n" << indent << "};\n\n"; + + output << indent << "-- Boolean variables denoting entities available for each agent --\n\n"; + + for (Process proc=0; procgetNumberOfProcesses(); ++proc) { + for (const Entity & e : procInputs[proc]) { + output << indent << rs->processes_ids[proc] + << "_" << rs->entities_ids[e] << ": 0..1;\n"; + } + + output << "\n"; + } + + output << indent << "-- Boolean variables denoting which agents are active --\n\n"; + + for (const string & procName : rs->processes_ids) + output << indent << "Active_" << procName << ": 0..1;\n"; + + output << "end Obsvars\n\n" + << "Vars:\n\n"; + + output << indent << "-- Context automaton details --\n\n"; + + output << indent << "state: {"; + + for (size_t i=0; ictx_aut->statesCount(); ++i) { + string stName = rs->ctx_aut->getStateName(i); + + // Skip the dummy state used for closure + if (stName == "T") + continue; + + if (i) output << ", "; + + output << stName; + } + + output << "};\n"; + + // Add dummy transition leading to the initial state (no agent is active) + output << indent << "transition: {" + << "\n" << indent << indent << "init_0"; + + int trId {1}; + + for (const CtxAutTransition & trans : rs->ctx_aut->transitions) { + string dstName = rs->ctx_aut->getStateName(trans.dst_state); + + if (dstName == "T") + continue; + + output << ","; + output << "\n" << indent << indent << rs->ctx_aut->getStateName(trans.src_state) + << "_" << dstName << "_" << trId; + ++trId; + } + + output << indent << "\n};\n\n"; + + output << indent << "-- Boolean variables denoting existence of entities in the environment.\n\n"; + + for (const string & entityName : rs->entities_ids) + output << indent << entityName << " : 0..1;\n"; + + output << "end Vars" << endl; +} + + +void RSExporter::exportEnvironmentActions() { + output << "\nActions = {\n" + << indent << "init, clear, select_active_agents, activate_agents,\n" + << indent << "distribute_local, add_context, distribute_global, produce,\n"; + + for (Process proc=0; procgetNumberOfProcesses(); ++proc) { + for (const Entity & e : procOutputs[proc]) { + output << indent << "collect_" << rs->processes_ids[proc] + << "_" << rs->entities_ids[e] << ",\n"; + } + } + + output << indent << "finalize, sleep\n};\n"; +} + + +void RSExporter::exportEnvironmentProtocol() { + output << "\nProtocol:\n" + << indent << "mode=clear : {clear};\n" + << indent << "mode=select_active_agents : {select_active_agents};\n" + << indent << "mode=activate_agents : {activate_agents};\n\n"; + + for (Process proc=0; procgetNumberOfProcesses(); ++proc) { + for (const Entity & e : procOutputs[proc]) { + output << indent << "mode=collect_" << rs->processes_ids[proc] + << "_" << rs->entities_ids[e] + << ": {collect_" << rs->processes_ids[proc] + << "_" << rs->entities_ids[e] << "};\n"; + } + + output << "\n"; + } + + output << indent << "mode=distribute_local : {distribute_local};\n" + << indent << "mode=add_context : {add_context};\n" + << indent << "mode=distribute_global : {distribute_global};\n" + << indent << "mode=produce : {produce};\n" + << indent << "mode=finalize : {finalize};\n\n" + << indent << "Other : {sleep};\n" + << "end Protocol\n"; +} + + +void RSExporter::exportEnvironmentEvolution() { + output << "\nEvolution:\n\n"; + + //-------------------------------------------------------------------------------- + // Reset the environment state after the previous computation step. + // (reset agent activity status, availability of entities, etc.) + //-------------------------------------------------------------------------------- + + output << indent <<"--------------------------------------------------------------------------------\n" + << indent <<"-- Reset the environment state after the previous computation step.\n" + << indent <<"-- (reset agent activity status, availability of entities, etc.)\n" + << indent <<"--------------------------------------------------------------------------------\n\n" + << indent; + + for (const string & procName : rs->processes_ids) + output << "Active_" << procName << "=0 and "; + + output << "\n" << indent << indent; + + for (const string & entityName : rs->entities_ids) + output << entityName << "=0 and "; + + output << "\n" << indent << indent << "mode=select_active_agents\n" + << indent << indent << indent << "if mode=clear;\n\n"; + + //-------------------------------------------------------------------------------- + // Active agents are selected based on the current context automaton transition. + //-------------------------------------------------------------------------------- + + output << indent <<"--------------------------------------------------------------------------------\n" + << indent <<"-- Active agents are selected based on the current context automaton transition.\n" + << indent <<"--------------------------------------------------------------------------------\n\n" + << indent; + + // For the dummy initial transition leading to the initial state no agent is active + for (Process proc=0; procgetNumberOfProcesses(); ++proc) + output << "Active_" << rs->getProcessName(proc) << "=0 and "; + + output << "mode=activate_agents\n" + << indent << indent << "if mode=select_active_agents and transition=init_0;\n\n" << indent; + + unsigned transNo = 1; + + for (const CtxAutTransition & trans : rs->ctx_aut->transitions) { + vector isActive(rs->getNumberOfProcesses(), false); + + string dstName = rs->ctx_aut->getStateName(trans.dst_state); + + if (dstName == "T") + continue; + + for (const auto & entry : trans.ctx) + isActive[entry.first] = true; + + for (Process proc=0; procgetNumberOfProcesses(); ++proc) + output << "Active_" << rs->getProcessName(proc) << "=" + << (isActive[proc] ? "1" : "0") + << " and "; + + output << "mode=activate_agents\n" + << indent << indent << "if mode=select_active_agents and transition=" + << rs->ctx_aut->getStateName(trans.src_state) << "_" + << rs->ctx_aut->getStateName(trans.dst_state) << "_" + << to_string(transNo) << ";\n\n" << indent; + + ++transNo; + } + + //-------------------------------------------------------------------------------- + // Conduct a series of queries to active agents regarding products + // that may have been produced in the last step when they were active. + //-------------------------------------------------------------------------------- + + output << "--------------------------------------------------------------------------------\n" + << indent << "-- Conduct a series of queries to active agents regarding products\n" + << indent << "-- that may have been produced in the last step when they were active.\n" + << indent << "--------------------------------------------------------------------------------\n\n"; + + vector> procProducts; + + for (Process proc=0; proc < rs->getNumberOfProcesses(); ++proc) + for (const Entity & e : procOutputs[proc]) + procProducts.push_back(make_pair(proc, e)); + + output << indent << "mode=collect_" + << rs->getProcessName(procProducts[0].first) << "_" + << rs->getEntityName(procProducts[0].second) << "\n" + << indent << indent << "if mode=activate_agents;\n\n"; + + for (unsigned pairIdx=1; pairIdx < procProducts.size(); ++pairIdx) { + output << indent << rs->getEntityName(procProducts[pairIdx-1].second) + << "=1 and mode=collect_" + << rs->getProcessName(procProducts[pairIdx].first) << "_" + << rs->getEntityName(procProducts[pairIdx].second) << "\n" + << indent << indent << "if mode=collect_" + << rs->getProcessName(procProducts[pairIdx-1].first) << "_" + << rs->getEntityName(procProducts[pairIdx-1].second) + << " and " << rs->getProcessName(procProducts[pairIdx-1].first) + << ".Action=produce_" + << rs->getEntityName(procProducts[pairIdx-1].second) + << ";\n"; + + output << indent << "mode=collect_" + << rs->getProcessName(procProducts[pairIdx].first) << "_" + << rs->getEntityName(procProducts[pairIdx].second) << "\n" + << indent << indent << "if mode=collect_" + << rs->getProcessName(procProducts[pairIdx-1].first) << "_" + << rs->getEntityName(procProducts[pairIdx-1].second) + << " and (" << rs->getProcessName(procProducts[pairIdx-1].first) + << ".Action=not_produce_" + << rs->getEntityName(procProducts[pairIdx-1].second) + << " or " << rs->getProcessName(procProducts[pairIdx-1].first) + << ".Action=sleep);\n\n"; + } + + output << indent << rs->getEntityName(procProducts.rbegin()->second) + << "=1 and mode=distribute_local\n" + << indent << indent << "if mode=collect_" + << rs->getProcessName(procProducts.rbegin()->first) << "_" + << rs->getEntityName(procProducts.rbegin()->second) + << " and " << rs->getProcessName(procProducts.rbegin()->first) + << ".Action=produce_" + << rs->getEntityName(procProducts.rbegin()->second) + << ";\n"; + + output << indent << "mode=distribute_local\n" + << indent << indent << "if mode=collect_" + << rs->getProcessName(procProducts.rbegin()->first) << "_" + << rs->getEntityName(procProducts.rbegin()->second) + << " and (" << rs->getProcessName(procProducts.rbegin()->first) + << ".Action=not_produce_" + << rs->getEntityName(procProducts.rbegin()->second) + << " or " << rs->getProcessName(procProducts.rbegin()->first) + << ".Action=sleep);\n\n"; + + //-------------------------------------------------------------------------------- + // The entities available in the environment are distributed between the agents. + //-------------------------------------------------------------------------------- + + output << indent <<"--------------------------------------------------------------------------------\n" + << indent <<"-- The entities available in the environment are distributed between the agents. \n" + << indent <<"--------------------------------------------------------------------------------\n\n" + << indent; + + bool first = true; + + for (Process proc=0; procgetNumberOfProcesses(); ++proc) { + for (const Entity & e : procInputs[proc]) { + if (!first) + output << " and "; + else + first = false; + + output << rs->getProcessName(proc) << "_" << rs->getEntityName(e) << "=" << rs->getEntityName(e); + } + + output << "\n" << indent; + } + + output << "and mode=add_context\n" << indent << indent + << "if mode=distribute_local;\n\n"; + + //-------------------------------------------------------------------------------- + // A set of additional entities, if any, is provided for each active agent + // following the context automaton transition description. + //-------------------------------------------------------------------------------- + + output << indent <<"--------------------------------------------------------------------------------\n" + << indent <<"-- A set of additional entities, if any, is provided for each active agent\n" + << indent <<"-- following the context automaton transition description.\n" + << indent <<"--------------------------------------------------------------------------------\n\n" + << indent; + + transNo = 1; + vector> noCtxTrans; + + for (const CtxAutTransition & trans : rs->ctx_aut->transitions) { + if (rs->ctx_aut->getStateName(trans.dst_state) == "T") + continue; + + string ctxStr {""}; + string separator {""}; + first = true; + + for (const auto & procCtx : trans.ctx) { + for (auto ent : procCtx.second) { + if (first) { + first = false; + separator = ""; + } + else + separator = "and "; + + ctxStr += separator + rs->getProcessName(procCtx.first) + "_" + rs->getEntityName(ent) + "=1 "; + } + } + + if (ctxStr.length() > 0) + output << indent << ctxStr + << "and mode=distribute_global\n" << indent << indent + << "if mode=add_context and transition=" + << rs->ctx_aut->getStateName(trans.src_state) << "_" << rs->ctx_aut->getStateName(trans.dst_state) << "_" << transNo + << ";\n\n"; + else + noCtxTrans.push_back({trans.src_state, trans.dst_state, transNo}); + + ++transNo; + } + + //-------------------------------------------------------------------------------- + // Mode change for transitions having no additional entities in the context + //-------------------------------------------------------------------------------- + + if (noCtxTrans.size() > 0) { + output << indent << "mode=distribute_global if mode=add_context and\n" + << indent << indent << " (transition=init_0"; + + for (const auto & trans : noCtxTrans) { + output << " or transition=" << rs->ctx_aut->getStateName(get<0>(trans)) + << "_" << rs->ctx_aut->getStateName(get<1>(trans)) + << "_" << get<2>(trans); + } + + output << ");\n\n"; + } + + //-------------------------------------------------------------------------------- + // Notify active agents to execute all possible reactions. + //-------------------------------------------------------------------------------- + + output << indent << "--------------------------------------------------------------------------------\n" + << indent <<"-- Notify active agents to execute all possible reactions.\n" + << indent <<"--------------------------------------------------------------------------------\n\n" + << indent; + + output << "mode=produce if mode=distribute_global;\n\n"; + + output << indent << "mode=finalize\n" + << indent << indent << "if mode=produce;\n\n"; + + //-------------------------------------------------------------------------------- + // Select next computation step (choose context automaton transition). + //-------------------------------------------------------------------------------- + + output << indent <<"--------------------------------------------------------------------------------\n" + << indent <<"-- Select next computation step (choose context automaton transition).\n" + << indent <<"--------------------------------------------------------------------------------\n\n" + << indent; + + transNo = 1; + + for (const CtxAutTransition & trans : rs->ctx_aut->transitions) { + if (rs->ctx_aut->getStateName(trans.dst_state) == "T") + continue; + + output << indent << "mode=clear and state=" + << rs->ctx_aut->getStateName(trans.dst_state) + << " and transition=" + << rs->ctx_aut->getStateName(trans.src_state) << "_" + << rs->ctx_aut->getStateName(trans.dst_state) << "_" + << transNo << "\n" << indent << indent; + + output << "\n" << indent << indent << "if mode=finalize and state=" + << rs->ctx_aut->getStateName(trans.src_state); + + if (trans.state_constr) + output << " and " << stateConstrToStr(trans.state_constr); + + output << ";\n\n"; + + ++transNo; + } + + output << "end Evolution\n"; +} + + +void RSExporter::exportEnvironmentInitState() { + output << indent << "Environment.mode=clear\n\n"; + + for (const string & procName : rs->processes_ids) + output << indent << indent << "and Environment.Active_" << procName << "=0\n"; + + output << "\n"; + + for (const string & entName : rs->entities_ids) + output << indent << indent << "and Environment." << entName << "=0\n"; + + output << "\n"; + + for (Process proc=0; procgetNumberOfProcesses(); ++proc) { + for (const Entity & e : procInputs[proc]) + output << indent << indent << "and Environment." << rs->getProcessName(proc) << "_" + << rs->entities_ids[e] << "=0\n"; + + output << "\n"; + } + + State initState = rs->ctx_aut->getInitState(); + output << indent << indent + << "and Environment.state=" + << rs->ctx_aut->getStateName(initState) << "\n" + << indent << indent << "and Environment.transition=init_0"; +} + + +void RSExporter::exportAgent(const Process & proc) { + output << "\nAgent " << rs->processes_ids[proc] << "\n\n"; + + exportAgentVars(proc); + exportAgentProtocol(proc); + exportAgentEvolution(proc); + + output << "end Agent\n" << endl; +} + + +void RSExporter::exportAgentVars(const Process & proc) { + output << "Vars:\n" + << indent << "isActive: 0..1;\n"; + + for (const Entity & e : procInputs[proc]) { + output << indent << rs->entities_ids[e] << "_in: 0..1;\n"; + } + + for (const Entity & e : procOutputs[proc]) { + output << indent << rs->entities_ids[e] << "_out: 0..1;\n"; + } + + output << "end Vars\n\n"; + + output << "Actions = {\n"; + + for (const Entity & e : procOutputs[proc]) { + output << indent << "produce_" << rs->entities_ids[e] + << ", not_produce_" << rs->entities_ids[e] << ",\n"; + } + + output << indent << "sleep\n};\n\n"; +} + + +void RSExporter::exportAgentProtocol(const Process & proc) { + string procName = rs->processes_ids[proc]; + + output << "Protocol:\n"; + + for (const Entity & e : procOutputs[proc]) { + string entityName = rs->entities_ids[e]; + + output << indent << entityName << "_out=1 and (Environment.mode=collect_" << procName << "_" << entityName + << " and isActive=1): {produce_" << entityName << "};\n" + << indent << entityName << "_out=0 and (Environment.mode=collect_" << procName << "_" << entityName + << " and isActive=1): {not_produce_" << entityName << "};\n\n"; + } + + output << indent << "Other: {sleep};\n" + << "end Protocol\n\n"; +} + + +void RSExporter::exportAgentEvolution(const Process & proc) { + string procName = rs->processes_ids[proc]; + + output << "Evolution:\n\n"; + + //-------------------------------------------------------------------------------- + // Set the agent's activity status. + //-------------------------------------------------------------------------------- + + output << indent <<"--------------------------------------------------------------------------------\n" + << indent <<"-- Set the agent's activity status.\n" + << indent <<"--------------------------------------------------------------------------------\n\n"; + + output << indent << "isActive=Environment.Active_" << procName << " "; + + for (const Entity & e : procInputs[proc]) + output << "and " << rs->entities_ids[e] << "_in=0 "; + + output << "\n" << indent << indent << "if Environment.Action=activate_agents;\n\n"; + + bool first {true}; + + //-------------------------------------------------------------------------------- + // Synchronise with the environment state. + //-------------------------------------------------------------------------------- + + output << indent << "--------------------------------------------------------------------------------\n" + << indent <<"-- Synchronise with the environment state.\n" + << indent <<"--------------------------------------------------------------------------------\n\n"; + + for (const Entity & e : procInputs[proc]) { + string entityName = rs->entities_ids[e]; + + if (first) { + first = false; + output << indent; + } + else { + output << "and "; + } + + output << entityName << "_in=Environment." << procName << "_" << entityName << " "; + } + + output << "\n" << indent << indent << "if isActive=1 and Environment.Action=distribute_global;\n\n"; + + exportAgentReactions(proc); + + output << "end Evolution\n\n"; +} + + +void RSExporter::exportAgentReactions(const Process & proc) { + output << indent <<"--------------------------------------------------------------------------------\n" + << indent <<"-- Agent's reactions.\n" + << indent <<"--------------------------------------------------------------------------------\n\n"; + + // For each entity produced accumulate all the ways it may be produced + + map sources; + + for (const auto & reaction : rs->proc_reactions[proc]) { + string rctCond = reactionCondToStr(reaction); + + for (const Entity & e : reaction.prod) { + string rctProduct = rs->getEntityName(e); + + if (sources.count(rctProduct)) + sources[rctProduct] = appendCondition(sources[rctProduct], rctCond); + else + sources[rctProduct] = rctCond; + } + } + + // Output a single formula combining all the agent's reactions + + bool moreReactions {false}; + + for (const auto & entry : sources) { + if (moreReactions) + output << indent << indent << "and\n"; + else + moreReactions = true; + + output << indent << entry.first << "_out = " << entry.second << "\n"; + } + + output << indent << indent << "if isActive=1 and Environment.Action=produce;\n\n"; +} + + +void RSExporter::exportAgentInitState(const Process & proc) { + string procName = rs->processes_ids[proc]; + + output << "\n\n" << indent << indent << "and " << procName << ".isActive=0"; + + for (const Entity & e : procInputs[proc]) { + output << "\n" << indent << indent << "and " << procName << "." << rs->entities_ids[e] << "_in=0"; + } + + for (const Entity & e : procOutputs[proc]) { + output << "\n" << indent << indent << "and " << procName << "." << rs->entities_ids[e] << "_out=0"; + } +} + + +void RSExporter::parseFormulas() { + for (const auto & prop : drv->properties) { + formulas.push_back(formulaToStr(prop.second)); + } +} + + +void RSExporter::exportEvaluation() { + output << "\nEvaluation\n"; + + for (const auto & a : atoms) + output << indent << a.second << " if " << a.first << ";\n"; + + output << "end Evaluation" << endl; +} + + +void RSExporter::exportInitStates() { + output << "\nInitStates\n"; + + exportEnvironmentInitState(); + + for (Process proc=0; procgetNumberOfProcesses(); ++proc) + exportAgentInitState(proc); + + output << ";\n" + << "end InitStates" << endl; +} + + +void RSExporter::exportFormulae() { + output << "\nFormulae\n"; + + for (const string & fStr : formulas) + output << indent << fStr << ";\n"; + + output << "end Formulae" << endl; +} + + +/* State constraints are required to be in conjunctive normal form. */ +string RSExporter::stateConstrToStr(const StateConstr * guard) { + string expr; + + switch (guard->oper) { + case STC_PV: + return guard->proc_name + "_" + guard->entity_name + "=1"; + + case STC_TF: + return guard->tf ? "true" : "false"; + + case STC_AND: + return "(" + stateConstrToStr(guard->arg[0]) + " and " + stateConstrToStr(guard->arg[1]) + ")"; + + case STC_OR: + return "(" + stateConstrToStr(guard->arg[0]) + " or " + stateConstrToStr(guard->arg[1]) + ")"; + + case STC_NOT: + expr = stateConstrToStr(guard->arg[0]); + return expr.substr(0, expr.length()-1) + "0"; + + default: + return "??"; + assert(0); + } +} + + +string RSExporter::reactionCondToStr(const Reaction & reaction) { + string cond; + + bool moreRctts {false}; + + for (const Entity & e : reaction.rctt) { + if (moreRctts) + cond += "*"; + else + moreRctts = true; + + cond += rs->getEntityName(e) + "_in"; + } + + for (const Entity & e : reaction.inhib) + cond += "*(1-" + rs->getEntityName(e) + "_in)"; + + return cond; +} + + +string RSExporter::appendCondition(string & oldCond, string & newCond) { + return "(" + oldCond + ") + " + newCond + " - (" + oldCond + ") * " + newCond; +} + + +string RSExporter::formulaToStr(const FormRSCTLK * form) { + string varStr; + string labelStr; + + switch (form->oper) { + + case RSCTLK_PV: // propositional variable + varStr = form->proc_name + "." + form->entity_name + "_out=1"; + + if (atoms.find(varStr) == atoms.end()) { + labelStr = "atomic_" + to_string(atoms.size()); + atoms[varStr] = labelStr; + } + else { + labelStr = atoms[varStr]; + } + + return labelStr; + + case RSCTLK_AND: + return "(" + formulaToStr(form->arg[0]) + " and " + formulaToStr(form->arg[1]) + ")"; + + case RSCTLK_OR: + return "(" + formulaToStr(form->arg[0]) + " or " + formulaToStr(form->arg[1]) + ")"; + + case RSCTLK_XOR: + return "((" + formulaToStr(form->arg[0]) + " or " + formulaToStr(form->arg[1]) + ") and !(" + + formulaToStr(form->arg[0]) + " and " + formulaToStr(form->arg[1]) + "))"; + + case RSCTLK_NOT: + return "!(" + formulaToStr(form->arg[0]) +")"; + + case RSCTLK_IMPL: + return "(" + formulaToStr(form->arg[0]) + " -> " + formulaToStr(form->arg[1]) + ")"; + + case RSCTLK_EG: // Existential... + return "EG(" + formulaToStr(form->arg[0]) + ")"; + + case RSCTLK_EU: + return "E(" + formulaToStr(form->arg[0]) + " U " + formulaToStr(form->arg[1]) + ")"; + + case RSCTLK_EX: + return "EX(" + formulaToStr(form->arg[0]) + ")"; + + case RSCTLK_EF: + return "EF(" + formulaToStr(form->arg[0]) + ")"; + + case RSCTLK_AG: // Universal... + return "AG(" + formulaToStr(form->arg[0]) + ")"; + + case RSCTLK_AU: + return "A(" + formulaToStr(form->arg[0]) + " U " + formulaToStr(form->arg[1]) + ")"; + + case RSCTLK_AX: + return "AX(" + formulaToStr(form->arg[0]) + ")"; + + case RSCTLK_AF: + return "AF(" + formulaToStr(form->arg[0]) + ")"; + + case RSCTLK_UK: // Epistemic operators + return "K(" + form->getSingleAgent() + ", " + formulaToStr(form->arg[0]) + ")"; + + default: + assert(0); + return "??"; + } +} diff --git a/reactics-bdd/export.hh b/reactics-bdd/export.hh new file mode 100644 index 0000000..b8b0714 --- /dev/null +++ b/reactics-bdd/export.hh @@ -0,0 +1,55 @@ + +#ifndef EXPORT_HH +#define EXPORT_HH + +#include +#include "types.hh" +#include "mc.hh" +#include "rs.hh" +#include "rsin_driver.hh" + + +class RSExporter { +public: + RSExporter(RctSys *rs, rsin_driver *drv, std::ostream & outStream = std::cout); + + void exportToISPL(); +private: + RctSys *rs; + rsin_driver *drv; + string indent {" "}; + std::ostream &output; + + void exportEnvironment(); + void exportAgent(const Process & proc); + + void exportEnvironmentVars(); + void exportEnvironmentActions(); + void exportEnvironmentProtocol(); + void exportEnvironmentEvolution(); + void exportEnvironmentInitState(); + + void exportAgentVars(const Process & proc); + void exportAgentProtocol(const Process & proc); + void exportAgentEvolution(const Process & proc); + void exportAgentReactions(const Process & proc); + void exportAgentInitState(const Process & proc); + + void parseFormulas(); + void exportEvaluation(); + void exportInitStates(); + void exportFormulae(); + + std::string stateConstrToStr(const StateConstr * guard); + std::string reactionCondToStr(const Reaction & reaction); + std::string appendCondition(std::string & oldCond, std::string & newCond); + std::string formulaToStr(const FormRSCTLK * form); + + EntitiesForProc procInputs; + EntitiesForProc procOutputs; + + std::map atoms; + std::vector formulas; +}; + +#endif \ No newline at end of file diff --git a/reactics-bdd/formrsctlk.hh b/reactics-bdd/formrsctlk.hh index 2eae014..b9091fd 100644 --- a/reactics-bdd/formrsctlk.hh +++ b/reactics-bdd/formrsctlk.hh @@ -62,9 +62,12 @@ using std::endl; typedef std::set Agents_f; class StateConstr; +class RSExporter; class FormRSCTLK { + friend class RSExporter; + Oper oper; FormRSCTLK *arg[2]; std::string entity_name; diff --git a/reactics-bdd/reactics.cc b/reactics-bdd/reactics.cc index 6b1a3bd..fddd010 100644 --- a/reactics-bdd/reactics.cc +++ b/reactics-bdd/reactics.cc @@ -16,6 +16,7 @@ int main(int argc, char **argv) bool rstl_model_checking = false; bool reach_states = false; bool reach_states_succ = false; + bool export_to_ispl = false; bool bmc = true; bool benchmarking = false; bool dump_help_message = false; @@ -31,7 +32,7 @@ int main(int argc, char **argv) int c; int option_index = 0; - while ((c = getopt_long(argc, argv, "c:bBmpPrsStTvxzXh", long_options, + while ((c = getopt_long(argc, argv, "c:bBmpPrsStTvxzXhe", long_options, &option_index)) != -1) { switch (c) { case 0: @@ -52,6 +53,10 @@ int main(int argc, char **argv) break; + case 'e': + export_to_ispl = true; + break; + //case 'b': // printf("-b with %s\n", optarg); // break; @@ -136,8 +141,8 @@ int main(int argc, char **argv) } if (!(reach_states || reach_states_succ || rstl_model_checking - || show_reactions || print_parsed_sys)) { - FERROR("No task specified: -c, -P, -r, or -s needs to be used"); + || show_reactions || print_parsed_sys || export_to_ispl)) { + FERROR("No task specified: -c, -P, -r, -s, or -e needs to be used"); } if (opts->verbose > 0) { @@ -171,7 +176,7 @@ int main(int argc, char **argv) rs.printSystem(); } - if (reach_states || reach_states_succ || rstl_model_checking) { + if (reach_states || reach_states_succ || rstl_model_checking || export_to_ispl) { SymRS srs(&rs, opts); ModelChecker mc(&srs, opts); @@ -184,6 +189,11 @@ int main(int argc, char **argv) mc.printReachWithSucc(); } + if (export_to_ispl) { + RSExporter exp(&rs, &driver); + exp.exportToISPL(); + } + if (rstl_model_checking) { if (bmc) { cout << "Using BDD-based Bounded Model Checking" << endl; @@ -268,6 +278,8 @@ void print_help(std::string path_str) << " Benchmarking options:" << endl << " -m -- measure and display time and memory usage" << endl << " -B -- display an easy to parse summary (enables -m)" << endl + << " -e -- exports to ISPL (MCMAS input format)" << endl + << endl; } diff --git a/reactics-bdd/reactics.hh b/reactics-bdd/reactics.hh index c1e0d7c..59a1455 100644 --- a/reactics-bdd/reactics.hh +++ b/reactics-bdd/reactics.hh @@ -18,6 +18,7 @@ #include "rsin_driver.hh" #include "options.hh" #include "memtime.hh" +#include "export.hh" #define VERSION "2.0" //#define AUTHOR "Artur Meski " diff --git a/reactics-bdd/rs.hh b/reactics-bdd/rs.hh index 45dd971..27ace9b 100644 --- a/reactics-bdd/rs.hh +++ b/reactics-bdd/rs.hh @@ -28,6 +28,7 @@ class RctSys { friend class SymRS; friend class SymRSstate; + friend class RSExporter; public: RctSys(void); diff --git a/reactics-bdd/stateconstr.hh b/reactics-bdd/stateconstr.hh index 31a038b..c6542ad 100644 --- a/reactics-bdd/stateconstr.hh +++ b/reactics-bdd/stateconstr.hh @@ -23,9 +23,12 @@ #define STC_IS_VALID(a) (STC_COND_1ARG(a) || STC_COND_2ARG(a) || (a) == STC_PV || (a) == STC_TF) class SymRS; +class RSExporter; class StateConstr { + friend class RSExporter; + Oper oper; StateConstr *arg[2]; std::string entity_name; -- 2.53.0 From f86316cdbd4f3b6dee490400d46c45c14afc2def Mon Sep 17 00:00:00 2001 From: Artur Meski Date: Tue, 29 Apr 2025 13:13:50 +0100 Subject: [PATCH 05/12] Context Automaton states in transitions (#2) --- reactics-bdd/mc.cc | 6 +++--- reactics-bdd/symrs.cc | 40 ++++++++++++++++++++++++++++++++++++++++ reactics-bdd/symrs.hh | 2 ++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/reactics-bdd/mc.cc b/reactics-bdd/mc.cc index 88f5e36..d1c7b4c 100644 --- a/reactics-bdd/mc.cc +++ b/reactics-bdd/mc.cc @@ -200,13 +200,13 @@ void ModelChecker::printReachWithSucc(void) t = unproc.PickOneMinterm(*pv); if (opts->backend_mode) { - cout << "G " << srs->decodedRctSysStateToStr(t) << endl; + cout << "G " << srs->decodedRctSysStateWithCtxAutToStr(t) << endl; } else { - cout << "Successors of " << srs->decodedRctSysStateToStr(t) << ":" << endl; + cout << "Successors of " << srs->decodedRctSysStateWithCtxAutToStr(t) << ":" << endl; } - srs->printDecodedRctSysStates(getSucc(t)); + srs->printDecodedRctSysWithCtxAutStates(getSucc(t)); unproc -= t; } diff --git a/reactics-bdd/symrs.cc b/reactics-bdd/symrs.cc index 98726e3..d99b38a 100644 --- a/reactics-bdd/symrs.cc +++ b/reactics-bdd/symrs.cc @@ -567,6 +567,21 @@ std::string SymRS::decodedRctSysStateToStr(const BDD &state) return s; } +std::string SymRS::decodedRctSysStateWithCtxAutToStr(const BDD &state) +{ + std::string s = "( "; + s += decodedRctSysStateToStr(state) + " "; + for (const auto &aut_state : rs->ctx_aut->states_ids) { + const auto state_id = rs->ctx_aut->getStateID(aut_state); + if (!(encCtxAutState(state_id) * state).IsZero()) { + s += aut_state + " "; + break; + } + } + s += ")"; + return s; +} + void SymRS::printDecodedRctSysStates(const BDD &states) { BDD unproc = states; @@ -591,6 +606,31 @@ void SymRS::printDecodedRctSysStates(const BDD &states) } } +void SymRS::printDecodedRctSysWithCtxAutStates(const BDD &states) +{ + BDD unproc = states; + + while (!unproc.IsZero()) { + BDD t = unproc.PickOneMinterm(*pv); + if (opts->backend_mode) + { + cout << "s " << decodedRctSysStateWithCtxAutToStr(t) << endl; + } + else + { + cout << decodedRctSysStateWithCtxAutToStr(t) << endl; + } + + if (opts->verbose > 9) { + BDD_PRINT(t); + cout << endl; + } + + unproc -= t; + } +} + + DecompReactions SymRS::getProductionConditions(Process proc_id) { DecompReactions dr; diff --git a/reactics-bdd/symrs.hh b/reactics-bdd/symrs.hh index 669910e..bcb5999 100644 --- a/reactics-bdd/symrs.hh +++ b/reactics-bdd/symrs.hh @@ -319,7 +319,9 @@ class SymRS BDD compContext(const BDD &context) const; std::string decodedRctSysStateToStr(const BDD &state); + std::string decodedRctSysStateWithCtxAutToStr(const BDD &state); void printDecodedRctSysStates(const BDD &states); + void printDecodedRctSysWithCtxAutStates(const BDD &states); DecompReactions getProductionConditions(Process proc_id); -- 2.53.0 From 092fd94b56b3ce000fe39d6517effca7db08c8b4 Mon Sep 17 00:00:00 2001 From: Artur Meski Date: Mon, 5 May 2025 14:26:40 +0100 Subject: [PATCH 06/12] README update: website --- README.md | 88 +------------------------------------------------------ 1 file changed, 1 insertion(+), 87 deletions(-) diff --git a/README.md b/README.md index 044b129..ee1bf24 100644 --- a/README.md +++ b/README.md @@ -6,91 +6,5 @@ The toolkit consists of two separate modules implementing: * Methods implemented using binary decision diagrams (BDD) for storing and manipulating the state space of the verified system. * Methods translating the verification problems into satisfiability modulo theories (SMT). -# Installation - -## Installing everything manually - -(to be added) - -## Examples - -The `examples` directory contains sample input files. - -### Multi-agent reaction systems (rsCTLK verification) - -To quickly test the BDD module you can perform verification of the TGC controller consiting of three trains: - -``` -$ ./reactics bdd -c f1 examples/bdd/tgc.rs -``` - -The above command tests the formula labelled `f1` in the input file. - -### Reachability - -To test the SMT module you can perform reachability verification of the scalable chain system: - -Running the benchmark without any arguments tells us what parameters are available: - -``` -$ ./reactics smt examples/smt/scalable_chain.py - - ------------------------------------------------ - -- ReactICS -- Reaction Systems Model Checker -- - ------------------------------------------------ - -arguments: -``` - -We may execute the benchmark for `chainLen=2`, `maxConc=3`, and `formulaNumber=1` using the following command: - -``` -$ ./reactics smt examples/smt/chain_reaction.py 2 3 1 -``` - -### rsLTL verification - -To test the SMT module for rsLTL verification the scalable chain system benchmark may be used. - - - -``` -$ ./reactics smt examples/smt/scalable_chain.py 2 5 1 -``` - - - -### Reaction synthesis - -To test the reaction synthesis approach on a mutual exclution protocol modelling three processes, run the following command (three processes, parametric verification, result optimised with OptSMT): - -``` -$ ./reactics smt examples/smt/mutex_param.py 3 p -o -``` - -To check the available parameters for the benchmark, we run it with `-h`: - -``` -$ ./reactics smt examples/smt/mutex_param.py -h - - ------------------------------------------------ - -- ReactICS -- Reaction Systems Model Checker -- - ------------------------------------------------ - -usage: mutex_param.py [-h] [-v] [-o] scaling {p,np-p,np-np} - -positional arguments: - scaling scaling parameter value - {p,np-p,np-np} Selects the mode: p - parameter synthesis (parametric - implementation), np-p - non-parametric with parametric - implementation (with the parameters substituted), np-np - - non-parametric with non-parametric implementation - (parameters substituted) - -optional arguments: - -h, --help show this help message and exit - -v, --verbose turn verbosity on - -o, --optimise minimise the parametric computation result -``` - +See: https://reactics.org -- 2.53.0 From 3237c2eae5e0e5a195f8e3d6a21e80de6eb68a21 Mon Sep 17 00:00:00 2001 From: Artur Meski Date: Mon, 5 May 2025 14:27:07 +0100 Subject: [PATCH 07/12] Clean-up --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ee1bf24..d9f809e 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ Reaction Systems Verification Toolkit The toolkit consists of two separate modules implementing: -* Methods implemented using binary decision diagrams (BDD) for storing and manipulating the state space of the verified system. + +* Methods using binary decision diagrams (BDD) for storing and manipulating the state space of the verified system. * Methods translating the verification problems into satisfiability modulo theories (SMT). See: https://reactics.org -- 2.53.0 From fafe8dbe07e346907fa38da36f9ebf9486ca91f2 Mon Sep 17 00:00:00 2001 From: Artur Meski Date: Thu, 8 May 2025 19:41:27 +0100 Subject: [PATCH 08/12] Optimisations are now enabled by default --- reactics-bdd/options.hh | 6 +++--- reactics-bdd/reactics.cc | 23 +++++++++++------------ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/reactics-bdd/options.hh b/reactics-bdd/options.hh index 2c05c49..4428e9f 100644 --- a/reactics-bdd/options.hh +++ b/reactics-bdd/options.hh @@ -24,10 +24,10 @@ class Options show_progress = false; measure = false; - part_tr_rel = false; + part_tr_rel = true; - reorder_reach = false; - reorder_trans = false; + reorder_reach = true; + reorder_trans = true; backend_mode = false; diff --git a/reactics-bdd/reactics.cc b/reactics-bdd/reactics.cc index fddd010..c86fec5 100644 --- a/reactics-bdd/reactics.cc +++ b/reactics-bdd/reactics.cc @@ -32,7 +32,7 @@ int main(int argc, char **argv) int c; int option_index = 0; - while ((c = getopt_long(argc, argv, "c:bBmpPrsStTvxzXhe", long_options, + while ((c = getopt_long(argc, argv, "c:bBmpPrsStTvXheEG", long_options, &option_index)) != -1) { switch (c) { case 0: @@ -102,13 +102,13 @@ int main(int argc, char **argv) opts->verbose++; break; - case 'x': - opts->part_tr_rel = true; + case 'E': + opts->reorder_reach = false; + opts->reorder_trans = false; break; - case 'z': - opts->reorder_reach = true; - opts->reorder_trans = true; + case 'G': + opts->part_tr_rel = false; break; case 'X': @@ -266,15 +266,14 @@ void print_help(std::string path_str) << endl << endl << " OTHER:" << endl << " -b -- disable bounded model checking (BMC) heuristic" << endl - << " -x -- use partitioned transition relation" << - endl - << " -z -- use reordering of the BDD variables" << endl - << " -v -- verbose (use more than once to increase verbosity)" << - endl + << " -v -- verbose (use more than once to increase verbosity)" << endl << " -p -- show progress (where possible)" << endl - << endl << " -X -- backend mode (makes output parsing easier)" << endl << endl + << " Optimisations:" << endl + << " -E -- disable auto-reordering optimisation of BDDs" << endl + << " -G -- disable partitioned transition relation optimisation" << endl + << endl << " Benchmarking options:" << endl << " -m -- measure and display time and memory usage" << endl << " -B -- display an easy to parse summary (enables -m)" << endl -- 2.53.0 From 90c8e4f4c071cab408cd1118f830596badf713ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Pi=C4=85tkowski?= Date: Wed, 28 May 2025 16:21:56 +0200 Subject: [PATCH 09/12] Added time and memory measurement for windows. (#4) --- reactics-bdd/memtime.hh | 43 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/reactics-bdd/memtime.hh b/reactics-bdd/memtime.hh index 992cbf0..69eac41 100644 --- a/reactics-bdd/memtime.hh +++ b/reactics-bdd/memtime.hh @@ -5,7 +5,12 @@ #define __MEMTIME_H__ #include +#ifdef __WIN32__ +#include +#include +#else #include +#endif #if defined(__APPLE__) #include #endif @@ -17,6 +22,25 @@ using namespace std; typedef long long int64; +#ifdef __WIN32__ + +static inline double cpuTime() +{ + FILETIME createTime, exitTime, kernelTime, userTime; + + if (!GetProcessTimes(GetCurrentProcess(), &createTime, &exitTime, &kernelTime, &userTime)) { + return 0.0; + } + + ULARGE_INTEGER u; + u.LowPart = userTime.dwLowDateTime; + u.HighPart = userTime.dwHighDateTime; + + return (double)u.QuadPart / 10e6; +} + +#else + static inline double cpuTime(void) { struct rusage ru; @@ -24,6 +48,8 @@ static inline double cpuTime(void) return (double)ru.ru_utime.tv_sec + (double)ru.ru_utime.tv_usec / 1000000; } +#endif + static inline int memReadStat(void) { char name[256]; @@ -47,11 +73,28 @@ static inline int memReadStat(void) return value; } + +#ifdef __WIN32__ + +static inline int64 memUsedInt64() +{ + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) { + return (int64) pmc.WorkingSetSize; + } + + return 0; +} + +#else + static inline int64 memUsedInt64() { return (int64)memReadStat() * (int64)getpagesize(); } +#endif + #if defined(__APPLE__) static inline double memUsed() -- 2.53.0 From 363446821e8896b22be6d8ce0400f7dbb61de0e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Pi=C4=85tkowski?= Date: Wed, 4 Jun 2025 20:21:51 +0200 Subject: [PATCH 10/12] Released version of GUI (#5) * The most recent version of ReactICS GUI. * Small fixes + folder structure update + help file included. * Added export to XML (for data sharing with GUI) * Released GUI version. * Automatic update of the reactants set panel + help content. --- reactics-bdd/export.cc | 103 +++ reactics-bdd/export.hh | 2 + reactics-bdd/reactics.cc | 17 +- .../help/img/ctx-automaton-edge-details.png | Bin 0 -> 7886 bytes .../help/img/ctx-automaton-panel.png | Bin 0 -> 3741 bytes .../help/img/ctx-automaton-state.png | Bin 0 -> 5242 bytes .../img/ctx-automaton-transition-edit.png | Bin 0 -> 12138 bytes reactics-gui/resources/help/index.html | 321 +++++++ reactics-gui/src/META-INF/MANIFEST.MF | 3 + .../reactics/ContextAutomatonEditor.java | 693 +++++++++++++++ .../reactics/ContextAutomatonGraph.java | 438 ++++++++++ .../reactics/CumulatedReactionsViewer.java | 13 + .../mat/martinp/reactics/FormulaEditor.java | 410 +++++++++ .../umk/mat/martinp/reactics/HelpWindow.java | 125 +++ .../mat/martinp/reactics/ProcessEditor.java | 427 ++++++++++ .../martinp/reactics/ReactantSetPanel.java | 49 ++ .../umk/mat/martinp/reactics/ReacticsGUI.java | 791 ++++++++++++++++++ .../mat/martinp/reactics/ReacticsRuntime.java | 198 +++++ .../reactics/ReactionSystemEditor.java | 409 +++++++++ .../martinp/reactics/TransitionEditor.java | 208 +++++ .../reactics/TransitionSystemViewer.java | 367 ++++++++ 21 files changed, 4571 insertions(+), 3 deletions(-) create mode 100644 reactics-gui/resources/help/img/ctx-automaton-edge-details.png create mode 100644 reactics-gui/resources/help/img/ctx-automaton-panel.png create mode 100644 reactics-gui/resources/help/img/ctx-automaton-state.png create mode 100644 reactics-gui/resources/help/img/ctx-automaton-transition-edit.png create mode 100644 reactics-gui/resources/help/index.html create mode 100644 reactics-gui/src/META-INF/MANIFEST.MF create mode 100644 reactics-gui/src/pl/umk/mat/martinp/reactics/ContextAutomatonEditor.java create mode 100644 reactics-gui/src/pl/umk/mat/martinp/reactics/ContextAutomatonGraph.java create mode 100644 reactics-gui/src/pl/umk/mat/martinp/reactics/CumulatedReactionsViewer.java create mode 100644 reactics-gui/src/pl/umk/mat/martinp/reactics/FormulaEditor.java create mode 100644 reactics-gui/src/pl/umk/mat/martinp/reactics/HelpWindow.java create mode 100644 reactics-gui/src/pl/umk/mat/martinp/reactics/ProcessEditor.java create mode 100644 reactics-gui/src/pl/umk/mat/martinp/reactics/ReactantSetPanel.java create mode 100644 reactics-gui/src/pl/umk/mat/martinp/reactics/ReacticsGUI.java create mode 100644 reactics-gui/src/pl/umk/mat/martinp/reactics/ReacticsRuntime.java create mode 100644 reactics-gui/src/pl/umk/mat/martinp/reactics/ReactionSystemEditor.java create mode 100644 reactics-gui/src/pl/umk/mat/martinp/reactics/TransitionEditor.java create mode 100644 reactics-gui/src/pl/umk/mat/martinp/reactics/TransitionSystemViewer.java diff --git a/reactics-bdd/export.cc b/reactics-bdd/export.cc index 6682cec..b8fa322 100644 --- a/reactics-bdd/export.cc +++ b/reactics-bdd/export.cc @@ -808,3 +808,106 @@ string RSExporter::formulaToStr(const FormRSCTLK * form) { return "??"; } } + + +void RSExporter::exportToXML() { + output << "\n" + << indent << "\n"; + + for (Process proc=0; procgetNumberOfProcesses(); ++proc) { + output << indent << indent << "processes_ids[proc] << "\">\n"; + + for (const auto & reaction : rs->proc_reactions[proc]) { + output << indent << indent << indent << "\n"; + + for (const Entity & e : reaction.rctt) { + output << indent << indent << indent << indent + << "" << rs->getEntityName(e) << "\n"; + } + + for (const Entity & e : reaction.inhib) { + output << indent << indent << indent << indent + << "" << rs->getEntityName(e) << "\n"; + } + + for (const Entity & e : reaction.prod) { + output << indent << indent << indent << indent + << "" << rs->getEntityName(e) << "\n"; + } + + output << indent << indent << indent << "\n"; + } + + output << indent << indent << "\n"; + } + + output << indent << "\n"; + + output << indent << "\n"; + + for (unsigned stId=0; stIdctx_aut->states_ids.size(); ++stId) { + if (rs->ctx_aut->states_ids[stId] == "T") + continue; + + output << indent << indent << "ctx_aut->states_ids[stId] << "\" x=\"0\" y=\"0\""; + + if (rs->ctx_aut->init_state_defined && stId == rs->ctx_aut->init_state_id) + output << " initial=\"true\""; + + output << "/>\n"; + } + + vector>> edges(rs->ctx_aut->states_ids.size()); + + for (const CtxAutTransition & trans : rs->ctx_aut->transitions) { + if (rs->ctx_aut->states_ids[trans.src_state] == "T" || rs->ctx_aut->states_ids[trans.dst_state] == "T") + continue; + + edges[trans.src_state][trans.dst_state].push_back(trans); + } + + for (unsigned src=0; srcctx_aut->getStateName(src) + << "\" to=\"" << rs->ctx_aut->getStateName(dst.first) << "\">\n"; + + for (const CtxAutTransition &trans : dst.second) { + output << indent << indent << indent << "procEntitiesToStr(trans.ctx) << "\""; + + if (trans.state_constr) + output << " guard=\"" << trans.state_constr->toStr() << "\""; + + output << "/>\n"; + } + + + output << indent << indent << "\n"; + } + } + + output << indent << "\n"; + + output << indent << "\n"; + + for (const auto & prop : drv->properties) { + string fStr = ""; + + for (const char ch : prop.second->toStr()) { + if (ch == '<') + fStr += "<"; + else if (ch =='>') + fStr += ">"; + else + fStr += ch; + } + + output << indent << indent << indent << "" + << fStr << "\n"; + + } + + output << indent << "\n"; + + output << "" << endl; +} diff --git a/reactics-bdd/export.hh b/reactics-bdd/export.hh index b8b0714..bc7d3a7 100644 --- a/reactics-bdd/export.hh +++ b/reactics-bdd/export.hh @@ -14,6 +14,8 @@ public: RSExporter(RctSys *rs, rsin_driver *drv, std::ostream & outStream = std::cout); void exportToISPL(); + void exportToXML(); + private: RctSys *rs; rsin_driver *drv; diff --git a/reactics-bdd/reactics.cc b/reactics-bdd/reactics.cc index c86fec5..90e1298 100644 --- a/reactics-bdd/reactics.cc +++ b/reactics-bdd/reactics.cc @@ -17,6 +17,7 @@ int main(int argc, char **argv) bool reach_states = false; bool reach_states_succ = false; bool export_to_ispl = false; + bool export_to_xml = false; bool bmc = true; bool benchmarking = false; bool dump_help_message = false; @@ -32,7 +33,7 @@ int main(int argc, char **argv) int c; int option_index = 0; - while ((c = getopt_long(argc, argv, "c:bBmpPrsStTvXheEG", long_options, + while ((c = getopt_long(argc, argv, "c:bBmpPrsStTvXheyEG", long_options, &option_index)) != -1) { switch (c) { case 0: @@ -57,6 +58,10 @@ int main(int argc, char **argv) export_to_ispl = true; break; + case 'y': + export_to_xml = true; + break; + //case 'b': // printf("-b with %s\n", optarg); // break; @@ -141,7 +146,7 @@ int main(int argc, char **argv) } if (!(reach_states || reach_states_succ || rstl_model_checking - || show_reactions || print_parsed_sys || export_to_ispl)) { + || show_reactions || print_parsed_sys || export_to_ispl || export_to_xml)) { FERROR("No task specified: -c, -P, -r, -s, or -e needs to be used"); } @@ -176,7 +181,7 @@ int main(int argc, char **argv) rs.printSystem(); } - if (reach_states || reach_states_succ || rstl_model_checking || export_to_ispl) { + if (reach_states || reach_states_succ || rstl_model_checking || export_to_ispl || export_to_xml) { SymRS srs(&rs, opts); ModelChecker mc(&srs, opts); @@ -194,6 +199,11 @@ int main(int argc, char **argv) exp.exportToISPL(); } + if (export_to_xml) { + RSExporter exp(&rs, &driver); + exp.exportToXML(); + } + if (rstl_model_checking) { if (bmc) { cout << "Using BDD-based Bounded Model Checking" << endl; @@ -269,6 +279,7 @@ void print_help(std::string path_str) << " -v -- verbose (use more than once to increase verbosity)" << endl << " -p -- show progress (where possible)" << endl << " -X -- backend mode (makes output parsing easier)" << endl + << " -y -- export to XML (for GUI tool)" << endl << endl << " Optimisations:" << endl << " -E -- disable auto-reordering optimisation of BDDs" << endl diff --git a/reactics-gui/resources/help/img/ctx-automaton-edge-details.png b/reactics-gui/resources/help/img/ctx-automaton-edge-details.png new file mode 100644 index 0000000000000000000000000000000000000000..858df8edb0b298a34fba4b26bdca58115c2306e7 GIT binary patch literal 7886 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 r%dT(q_&jS7f3nkF* literal 0 HcmV?d00001 diff --git a/reactics-gui/resources/help/img/ctx-automaton-panel.png b/reactics-gui/resources/help/img/ctx-automaton-panel.png new file mode 100644 index 0000000000000000000000000000000000000000..2fdd1f8ab1fceb30a2f6e0b6810de4cece4d2761 GIT binary patch literal 3741 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 Xl%7gson literal 0 HcmV?d00001 diff --git a/reactics-gui/resources/help/img/ctx-automaton-state.png b/reactics-gui/resources/help/img/ctx-automaton-state.png new file mode 100644 index 0000000000000000000000000000000000000000..fbafe425d14eec1d1ec0ee76b5d23eed0329860f GIT binary patch literal 5242 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 literal 0 HcmV?d00001 diff --git a/reactics-gui/resources/help/img/ctx-automaton-transition-edit.png b/reactics-gui/resources/help/img/ctx-automaton-transition-edit.png new file mode 100644 index 0000000000000000000000000000000000000000..888e93da73a94bb6d5706ab6c23e883203d1eb46 GIT binary patch literal 12138 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 literal 0 HcmV?d00001 diff --git a/reactics-gui/resources/help/index.html b/reactics-gui/resources/help/index.html new file mode 100644 index 0000000..d40c9a4 --- /dev/null +++ b/reactics-gui/resources/help/index.html @@ -0,0 +1,321 @@ + + + + + ReactICS GUI Help + + + +
+

ReactICS - Distributed Reaction Systems Model Checker

+
+ + +

+ ReactICS is a toolkit that allows for verification of Reaction Systems. + The toolkit consists of two separate modules implementing: +

    +
  • Methods using binary decision diagrams (BDD) + for storing and manipulating the state space of the verified system.
  • +
  • Methods translating the verification problems into satisfiability modulo theories (SMT).
  • +
+

+

+ ReactICS GUI offers a graphical user interface allowing for editing the reaction system details + and model checking using BDD module. +

+

+ More on ReactICS can be found at The Reactics Webpage. +

+ +
+
+

Table of contents

+ +
+ + +
+

Reaction Systems

+
+ +

+ Reaction Systems are a formalism inspired by the functioning of living cells. + They allow for specifying and analysing computational processes in which reactions operate on sets of molecules. +

+ +

+ The behaviour of a reaction system is determined by the interactions of its reactions, which are based on + the mechanisms of facilitation and inhibition. + The formal treatment of reaction systems is qualitative and there is no direct representation of the number + of molecules involved in reactions. +

+ +

+ A Distributed Reaction System is a computational model in which multiple autonomous processes (aka. agents) + interact locally through rule-based reactions. + Each agent operates independently (without any central controller—coordination), + processing inputs and producing outputs based on predefined reactions, + and their collective behavior leads to complex system dynamics. +

+ +

+ More on reaction system can be found at The Reaction System Webpage +

+ +
Back to top
+
+ +
+

Application functions

+
+ +

+ ReactICS GUI allows to: +

    +
  • Edit the reaction system structure (the number of processes/agents, reactions for each process/agent, etc.)
  • +
  • Edit the context automaton structure (adding/removing states and transitions, + edit context for active processes/agents, setting guards for transitions, etc.).
  • +
  • Preview of the state space represented as a transition system.
  • +
  • Verification of properties defined by formulas using rsCTLK semantics.
  • +
  • Save/load the reaction system structure to/from XML file.
  • +
  • Export/import the reaction system to/from RSSL file.
  • +
  • Export the reaction system structure and behaviour to ISPL file.
  • +
+

+ +
Back to top
+ +
+ + +
+

Distributed reaction system editor

+
+ + +

+ The structure of distributed reaction system can be edited using the following buttons from the distributed reaction system control panel: +

+
    +
  • New process - Creates a new empty process.
  • +
  • Remove - Removes selected processes.
  • +
  • Copy - Makes copies of selected processes (together with all reactions).
  • +
  • Rename - Renames a selected process. Note that process names should be unique.
  • +
  • Move up - Moves selected process up the list of all processes.
  • +
  • Move down - Moves selected process down the list of all processes.
  • +
+

+ +

+ The details of a single process can be edited using the following buttons in the process panel: +

+
    +
  • Add reaction - Creates a new reaction. The details of the reaction should be specified in dialog shown below.
  • +
  • Edit reaction - Allows to edit the details of a selected reaction using dialog shown below.
  • +
  • Remove reaction - Removes a selected reaction.
  • + +
+

+
+

+ Verification of formulas against a system specification is done after selecting desired formulas + and clicking Evaluate button. + The result of the verification (True/False), as well as time and memory used are presented next to each verified formula. +

+
+

+ Clicking the Reset button resets the status of the model checking. + Note that updating the reaction system specification (the list of processes, details of the reactions or the content automaton structure) + the results of model checking, if any, are reset automatically. +

+ + +
Back to top
+ +
+ + +
+

Context automaton editor

+
+ +

+ To create or edit the context automaton structure a proper edit mode should be set using the edit mode panel. +

+
+
+ +

+ The type of the newly created object (state or edge) is set by checking the proper toggle button. + With no button checked, one can transform the net by moving states around, etc. + (see below for more details). +

+ +
+ +

States

+ +

+ A new state is created by left-clicking the edit area (the main part of the component). + As it is customary in automata theory, states are numbered separately starting from 1. + The types of states are distinguished by shape and colour: + the initial state is depicted as a square, all other states are depicted as circles. + Moreover, hovering the mouse above a state allows to see its full description containing both + its name and its (distinct) number. +

+
+
+ +

+ The details of a state can be edited by right-clicking the existing + state and choosing the right option from the popup menu: +

    +
  • Set as initial - Makes the selected state the initial state of the context automaton.
  • +
  • Set label - Allows to set the custom label for the selected state. Note that state labels have to be unique.
  • +
+

+
+
+ +

Edges

+ +

+ A directed edge between two states is created by left-clicking both its endpoints in correct order. + Each edge may contain several transitions, each of them defined by a context (additional entities + provided for active processes/agents) and a guard (a condition required by the transition to execute). + The list of transitions may be edited by right-clicking the existing + edge and choosing the Nondeterministic transitions popup menu. +

+
+

+ Hovering the mouse above an edge allows to see the full list of transitions + including context and guards. +

+
+ +
+ +

Removing context automaton elements

+ +

+ Switching the toggle button Delete on allows removing context automaton elements. + Each left-clicked element is removed from the automaton grapn. + Moreover, the entire context automaton graph may be deleted by clicking the Clear button. +

+ +
+ +

Transforming the context automaton graph

+ +

+ Any state of the automaton may be picked and dragged around the edit area to obtain the desired graph shape. + It is also possible to select multiple states and move them together at the same time. + After CTRL + left-click on a particular state the entire automaton graph is shifted to be centered + on the clicked state. + Checking the Lock relative nodes positions checkbox locks the relative positions of the states. + In that case, dragging a single state moves the entire automaton graph. + Moreover, using mouse scroll the automaton graph view can be zoomed in and out. +

+ +
Back to top
+ +
+ + +
+

Transition system viewer

+
+ +

+ The component offers the visualisation of the transition system representing the state space of the reaction systems. + The initial state is depicted as a square, all other states are depicted as circles. + There is a directed edge connecting the state Si to the state Sj + if Sf is reachable from Si by a single computation step of the reaction system. +

+ +

+ The component has two separate instances represented by two separate tabs in the application window. + The first visualises the complete state space of the reaction system, + where each state is represented by sets of entities possessed by each agent and the current context automaton state. + The second visualises the compressed state space, where context automaton states are ignored + (states are represented only by the sets of entities possessed by each agent). +

+ +

+ Computation is done by pressing Update transition system button. + The reaction system structure should be loaded from the file or edited manually for the computation to be possible. +

+ +

+ Note that no matter in which component the Update transition system button + is pressed, the transition system structure will be updated in both. +

+ +

+ Hovering the mouse above a state allows to see its full description: entities possessed by each process/agent + and the context automaton state (not present in the case of the compressed graph). +

+
+ +
+ +

Transforming the transition system graph

+ +

+ Any state of the transition system may be picked and dragged around the edit area to obtain the desired graph shape. + It is also possible to select multiple states and move them together at the same time. + After CTRL + left-click on a particular state the entire transition system graph is shifted to be centered + on the clicked state. + Checking the Lock relative nodes positions checkbox locks the relative positions of the states. + In that case, dragging a single state moves the entire transition system graph. + Moreover, using mouse scroll the transition system graph view can be zoomed in and out. +

+ +
Back to top
+ +
+ +
+

Model checking

+
+ +

+Model checking is handled by the component at the bottom of the application window. +The properties of the analysed system are expressed as formulas following rsCTLK syntax. +

+

+The list of formulas may be modified using the following buttons: +

    +
  • Add formula - Creates an empty formula. The details of the formula should be specified in dialog shown below.
  • +
  • Edit formula - Allows to edit the details of a selected formula using dialog shown below.
  • +
  • Remove formulas - Removes selected formulas.
  • + +
+

+
+

+Verification of formulas against a system specification is done after selecting desired formulas +and clicking Evaluate button. +The result of the verification (True/False), as well as time and memory used are presented next to each verified formula. +

+
+

+Clicking the Reset button resets the status of the model checking. +Note that updating the reaction system specification (the list of processes, details of the reactions or the content automaton structure) +the results of model checking, if any, are reset automatically. +

+ +
Back to top
+ +
+
+ + \ No newline at end of file diff --git a/reactics-gui/src/META-INF/MANIFEST.MF b/reactics-gui/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000..b010a96 --- /dev/null +++ b/reactics-gui/src/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: pl.umk.mat.martinp.reactics.ReacticsGUI + diff --git a/reactics-gui/src/pl/umk/mat/martinp/reactics/ContextAutomatonEditor.java b/reactics-gui/src/pl/umk/mat/martinp/reactics/ContextAutomatonEditor.java new file mode 100644 index 0000000..4c1ee59 --- /dev/null +++ b/reactics-gui/src/pl/umk/mat/martinp/reactics/ContextAutomatonEditor.java @@ -0,0 +1,693 @@ +package pl.umk.mat.martinp.reactics; + +import edu.uci.ics.jung.algorithms.layout.FRLayout; +import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; +import edu.uci.ics.jung.algorithms.layout.Layout; +import edu.uci.ics.jung.algorithms.layout.StaticLayout; +import edu.uci.ics.jung.algorithms.layout.util.Relaxer; +import edu.uci.ics.jung.graph.Graph; +import edu.uci.ics.jung.graph.util.Context; +import edu.uci.ics.jung.graph.util.Pair; +import edu.uci.ics.jung.visualization.VisualizationViewer; +import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; +import edu.uci.ics.jung.visualization.control.ModalGraphMouse; +import edu.uci.ics.jung.visualization.decorators.EdgeShape; +import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; +import edu.uci.ics.jung.visualization.picking.PickedState; +import edu.uci.ics.jung.visualization.renderers.Renderer; +import org.apache.commons.collections15.Transformer; +import org.w3c.dom.Document; +import javax.swing.*; +import javax.swing.border.EtchedBorder; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.io.PrintWriter; +import java.util.*; + + +public class ContextAutomatonEditor extends JPanel implements RSObserver { + private enum EditorState {STATE, EDGE, DELETE, CLEAR, NONE} + + private static final long serialVersionUID = 1L; + + private ReactionSystem rs; + + //--------------------------------------------------------------- + // Visual configuration settings + //--------------------------------------------------------------- + private static final Color pickedNodeColor = Color.yellow; + //private static final Color pickedEdgeColor = Color.red; + private static final Color pickedEdgeColor = Color.black; + private static final Color edgeColor = Color.black; + private static final Color emptyEdgeColor = Color.lightGray; + private static final Font nodeLabelFont = new Font("Helvetica", Font.BOLD, 20); + private static final Font edgeLabelFont = new Font("Helvetica", Font.BOLD, 15); + private static final Font toolTipFont = new Font("Helvetica", Font.PLAIN, 14); + private static final float[] dash = {10.0f, 10.0f}; + private static final Stroke basicEdgeStroke = new BasicStroke(3.0f,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND); + private static final Stroke emptyEdgeStroke = new BasicStroke(3.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 10.0f, dash, 0.f); + private static final Stroke basicEdgeArrow = new BasicStroke(3.0f); + private static final Stroke emptyEdgeArrow = new BasicStroke(3.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 10.0f, dash, 10.0f); + + private static Dimension initialEditorSize; + + private VisualizationViewer graphViewer; + private Layout gLayout; + DefaultModalGraphMouse graphMouse; + private ContextAutomatonGraph graph; + + // Context menus for transitions, places and edges + private JPopupMenu stateContextMenu; + private JPopupMenu edgeContextMenu; + private TransitionEditor transitionEditor = null; + + private EditorState edtState; + private Vector modeButtons; + + private CAState lastPickedNode = null; + + + public ContextAutomatonEditor(Dimension size) { + this.setLayout(new BorderLayout()); + initialEditorSize = size; + + edtState = EditorState.NONE; + graph = new ContextAutomatonGraph(); + + createGraphEditor(); + createModeButtonsPanel(); + createContextMenus(); + + rs = ReactionSystem.getInstance(); + } + + public boolean isModified() { return graph.isModified(); } + + public void clearModificationStatus() { graph.clearModificationStatus(); } + + public String toRSSLString() { + return graph.toRSSLString(); + } + + public void exportToXML(PrintWriter output) { + updateNodesLocations(); + graph.exportToXML(output); + } + + public void loadFromXML(Document input) throws ContextAutomatonStructureError { + clear(); + graph.loadFromXML(input); + } + + + public void recalculateCoordinates() { + Layout autoLayout = new FRLayout(graph, graphViewer.getSize()); + autoLayout.initialize(); + + for (CAState state : graph.getVertices()) { + Point2D pos = autoLayout.transform(state); + state.updateLocation(pos); + } + } + + + public void clear() { + graph.clear(); + + for (EditorModeButton emb : modeButtons) + emb.setSelected(false); + + graphViewer.repaint(); + } + + public void lockNodesPositions(boolean lock) { + if (lock) + graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING); + else + graphMouse.setMode(ModalGraphMouse.Mode.PICKING); + } + + public Set getReactantsSet() { return graph.getReactantsSet(); } + + //-------------------------------------------------------------------------- + // Creates visualization viewer for context automaton graph display/edition + //-------------------------------------------------------------------------- + private void createGraphEditor() { + gLayout = new StaticLayout(graph, + new Transformer() { + public Point2D transform(CAState node) { + return node.getLocation(); + } + }); + + graphViewer = new VisualizationViewer(gLayout, initialEditorSize) { + public JToolTip createToolTip() { + JToolTip tooltip = super.createToolTip(); + tooltip.setFont(toolTipFont); + return tooltip; + } + }; + + // Nodes labels + graphViewer.getRenderContext().setVertexLabelTransformer( + new ToStringLabeller()); + graphViewer.getRenderer().getVertexLabelRenderer() + .setPosition(Renderer.VertexLabel.Position.CNTR); + graphViewer.getRenderContext().setVertexFontTransformer( + new Transformer() { + public Font transform(CAState v) { + return nodeLabelFont; + } + }); + + // Nodes colour, background, etc. + graphViewer.getRenderContext().setVertexIconTransformer(new Transformer() { + public Icon transform(final CAState v) { + Color nodeColor = null; + + if(graphViewer.getPickedVertexState().isPicked(v)) + nodeColor = pickedNodeColor; + else + nodeColor = v.getFillColor(); + + return (v.isInitial() ? new SquareIcon(nodeColor, v.getWidth()) : new CircleIcon(nodeColor, v.getWidth())); + + }}); + + // Edge color, shape and weight + graphViewer.getRenderContext().setEdgeDrawPaintTransformer(new Transformer() { + public Paint transform(CAEdge e) { + return edgeColor; + } + }); + + graphViewer.getRenderContext().setEdgeStrokeTransformer(new Transformer() { + public Stroke transform(CAEdge e) { + return basicEdgeStroke; + } + }); + + graphViewer.getRenderContext().setEdgeArrowStrokeTransformer(new Transformer() { + public Stroke transform(CAEdge e) { + return basicEdgeArrow; + } + }); + + graphViewer.getRenderContext().setArrowFillPaintTransformer(new Transformer() { + public Paint transform(CAEdge caEdge) { + if (caEdge.hasTransition()) + return edgeColor; + else + return emptyEdgeColor; + } + }); + + graphViewer.getRenderContext().setEdgeShapeTransformer(new ConditionalEdgeShape<>(1.5f)); + + // Edge labels + graphViewer.getRenderContext().setEdgeLabelTransformer(new ToStringLabeller()); + graphViewer.getRenderContext().getEdgeLabelRenderer().setRotateEdgeLabels(true); + graphViewer.getRenderContext().setLabelOffset(20); + + graphViewer.getRenderContext().setEdgeFontTransformer(new Transformer() { + public Font transform(CAEdge CAEdge) { + return edgeLabelFont; + } + }); + + // For interactive graph editing + graphMouse = new DefaultModalGraphMouse(); + graphViewer.setGraphMouse(graphMouse); + + graphMouse.setMode(ModalGraphMouse.Mode.PICKING); + + graphViewer.setVertexToolTipTransformer(new Transformer() { + public String transform(CAState caState) { + return caState.getTooltipText(); + } + }); + + graphViewer.setEdgeToolTipTransformer(new Transformer() { + public String transform(CAEdge caEdge) { + return caEdge.getTooltipText(); + } + }); + + graphViewer.setBackground(Color.white); + graphViewer.setBorder(new EtchedBorder(EtchedBorder.LOWERED)); + graphViewer.addMouseListener(new EditorMouseListener()); + + add(graphViewer,BorderLayout.CENTER); + } + + //--------------------------------------------------------------- + // Create panel with buttons alowing editing mode choice + //--------------------------------------------------------------- + private void createModeButtonsPanel() { + modeButtons = new Vector(); + modeButtons.add(new EditorModeButton("STATE",new CircleIcon(CAState.baseFillColor, 15), EditorState.STATE, 0)); + modeButtons.add(new EditorModeButton("EDGE", EditorState.EDGE, 1)); + modeButtons.add(new EditorModeButton("DELETE", EditorState.DELETE, 2)); + modeButtons.add(new EditorModeButton("CLEAR", EditorState.CLEAR, 3)); + + ModeButtonsListener mbListener = new ModeButtonsListener(); + + for(int i=0;i pickedVertexState = graphViewer.getPickedVertexState(); + Set pickedNodes = pickedVertexState.getPicked(); + Iterator it = pickedNodes.iterator(); + + if(!it.hasNext()) { + return; + } + + CAState editedNode = it.next(); + pickedVertexState.clear(); + + graph.setInitialState(editedNode); + } + }); + + JMenuItem setLabelItem = new JMenuItem("Set label"); + setLabelItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + PickedState pickedVertexState = graphViewer.getPickedVertexState(); + Set pickedNodes = pickedVertexState.getPicked(); + Iterator it = pickedNodes.iterator(); + + if(!it.hasNext()) { + return; + } + + CAState state = it.next(); + pickedVertexState.clear(); + + showStateLabelEditDialog(state); + } + }); + + stateContextMenu.add(stateInitialItem); + stateContextMenu.add(setLabelItem); + stateContextMenu.setVisible(false); + + edgeContextMenu = new JPopupMenu(); + JMenuItem edgeLabelEditItem = new JMenuItem("Nondeterministic transitions"); + edgeLabelEditItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + PickedState pickedEdgeState = graphViewer.getPickedEdgeState(); + Set pickedEdges = pickedEdgeState.getPicked(); + Iterator it = pickedEdges.iterator(); + + if(!it.hasNext()) { + return; + } + + CAEdge editedEdge = it.next(); + pickedEdgeState.clear(); + showEdgeLabelEditDialog(editedEdge); + } + }); + + edgeContextMenu.add(edgeLabelEditItem); + edgeContextMenu.setVisible(false); + + transitionEditor = new TransitionEditor(); + } + + private void showStateLabelEditDialog(CAState state) { + String idRegex = "[a-zA-Z][a-zA-Z_0-9:-]*"; + boolean idOk = false; + + do { + String newLabel = JOptionPane.showInputDialog(this, "State label", state.getLabel()); + + if (newLabel == null) + return; + + newLabel = newLabel.trim(); + + if (!newLabel.matches(idRegex)) { + JOptionPane.showMessageDialog(this, + "State label should start with a letter and may contain only:
" + + "letters, numbers, colons (:), underscores (_) and hyphens (-)", + "Error", JOptionPane.ERROR_MESSAGE); + } + else { + idOk = true; + state.setLabel(newLabel); + } + } + while (!idOk); + + graph.markAsModified(); + this.repaint(); + } + + //--------------------------------------------------------------- + // Creates a simple dialog window for editing edge labels + //--------------------------------------------------------------- + private void showEdgeLabelEditDialog(CAEdge edge) { + Pair endPoints = graph.getEndpoints(edge); + transitionEditor.showTransitionEditDialog(this, edge.getTransitions(), endPoints.getFirst().getLabel(), endPoints.getSecond().getLabel()); + graph.markAsModified(); + graphViewer.repaint(); + rs.notifyObservers(); + } + + /** Updates coordinates stored in each graph node (eg. after interactive graph modification). + * Called before each storing net structure to the file. */ + private void updateNodesLocations() { + Collection nodes = graph.getVertices(); + + for(CAState n : nodes) { + n.updateLocation(gLayout.transform(n)); + } + } + + /** For better vertex placement */ + private void relax() { + Layout layout = graphViewer.getGraphLayout(); + layout.initialize(); + layout.setSize(graphViewer.getSize()); + Relaxer relaxer = graphViewer.getModel().getRelaxer(); + if (relaxer != null) { + relaxer.stop(); + relaxer.prerelax(); + relaxer.relax(); + } + } + + public void onRSUpdate() { + repaint(); + } + + //--------------------------------------------------------------------------------------------- + + + static class EditorModeButton extends JToggleButton { + private EditorState mode; + int seqNum; + + public EditorModeButton(String label, EditorState mode) { + super(label); + + this.mode = mode; + seqNum = 0; + } + + public EditorModeButton(String label, EditorState mode, int seqNum) { + super(label); + + this.mode = mode; + this.seqNum = seqNum; + } + + public EditorModeButton(String label, Icon icon, EditorState mode, int seqNum) { + super(label, icon); + + this.mode = mode; + this.seqNum = seqNum; + } + + public EditorState getMode() { + return mode; + } + + public int getSeqNum() { + return seqNum; + } + } + + + //--------------------------------------------------------------------------------------------- + + + class ModeButtonsListener implements ActionListener { + + public void actionPerformed(ActionEvent changeEvent) { + EditorModeButton emb = (EditorModeButton) changeEvent.getSource(); + + if(emb.isSelected()) { + int num = emb.getSeqNum(); + + for(int i=0;i pickSupport = null; + Layout layout = null; + CAState node = null; + CAEdge edge = null; + + switch(edtState) { + case STATE: + try { + // Transform screen coordinates to layout coordinates. + // Necessary for possible automaton structure viewer scaling. + Point2D nodeLocation = graphViewer.getRenderContext().getMultiLayerTransformer().inverseTransform(clickPt); + graph.addState(new CAState(nodeLocation)); + } catch (ContextAutomatonStructureError e) { + JOptionPane.showMessageDialog(null, + e.getMessage(), + "Error", JOptionPane.ERROR_MESSAGE); + } + break; + + case EDGE: + PickedState pickedVertexState = graphViewer.getPickedVertexState(); + + pickSupport = graphViewer.getPickSupport(); + layout = graphViewer.getGraphLayout(); + node = pickSupport.getVertex(layout, clickPt.getX(), clickPt.getY()); + + if (node != null) { + if (lastPickedNode == null) { + lastPickedNode = node; + } else { + graph.addEdge(lastPickedNode, node); + lastPickedNode = null; + pickedVertexState.pick(node,false); + } + } else { + lastPickedNode = null; + } + + break; + + case DELETE: + pickSupport = graphViewer.getPickSupport(); + layout = graphViewer.getGraphLayout(); + node = pickSupport.getVertex(layout, clickPt.getX(), clickPt.getY()); + edge = pickSupport.getEdge(layout, clickPt.getX(), clickPt.getY()); + + if (node != null) { + Collection edges = graph.getIncidentEdges(node); + + for (CAEdge e : edges) + rs.removeCAEdge(e); + + graph.removeState(node); + } + else if (edge != null) { + graph.removeEdge(edge); + rs.removeCAEdge(edge); + } + + break; + + case NONE: + default: + break; + } + + me.consume(); + } + + private void rightButtonClicked(MouseEvent me) { + GraphElementAccessor pickSupport = graphViewer.getPickSupport(); + Layout layout = graphViewer.getGraphLayout(); + CAState node = pickSupport.getVertex(layout, me.getX(), me.getY()); + CAEdge edge = pickSupport.getEdge(layout, me.getX(), me.getY()); + + if (node != null) { + PickedState pickedVertexState = graphViewer.getPickedVertexState(); + pickedVertexState.clear(); + pickedVertexState.pick(node,true); + stateContextMenu.show(graphViewer, me.getX(), me.getY()); + } + else if (edge != null) { + PickedState pickedEdgeState = graphViewer.getPickedEdgeState(); + pickedEdgeState.clear(); + pickedEdgeState.pick(edge, true); + edgeContextMenu.show(graphViewer, me.getX(), me.getY()); + } + + me.consume(); + } + + }; + + //--------------------------------------------------------------------------------------------- + // For drawing the initial state + //--------------------------------------------------------------------------------------------- + + static class SquareIcon implements Icon { + private Color fillColor; + private int width; + + public SquareIcon(Color fColor, int width) { + fillColor = fColor; + this.width = width; + } + + public void paintIcon(Component c, Graphics g, int x, int y) { + g.setColor(fillColor); + g.fillRect(x, y, width, width); + g.setColor(Color.black); + g.drawRect(x, y, width, width); + } + + public int getIconWidth() { + return width; + } + + public int getIconHeight() { + return width; + } + } + + //--------------------------------------------------------------------------------------------- + // For drawing all non-initial states + //--------------------------------------------------------------------------------------------- + + static class CircleIcon implements Icon { + private Color fillColor; + private int radius; + + public CircleIcon(Color fColor, int radius) { + fillColor = fColor; + this.radius = radius; + } + + public void paintIcon(Component c, Graphics g, int x, int y) { + g.setColor(fillColor); + g.fillOval(x, y, radius, radius); + g.setColor(Color.black); + g.drawOval(x, y, radius, radius); + } + + public int getIconWidth() { + return radius; + } + + public int getIconHeight() { + return radius; + } + + } + +} + + +class ConditionalEdgeShape implements Transformer, E>, Shape> { + + private final EdgeShape.Loop loopShape; + private final Transformer, E>, Shape> defaultShape; + private final float loopScale; + + public ConditionalEdgeShape(float loopScale) { + this.loopShape = new EdgeShape.Loop<>(); + this.defaultShape = new EdgeShape.QuadCurve<>(); // or Line<> + this.loopScale = loopScale; + } + + public Shape transform(Context, E> context) { + Graph graph = context.graph; + E edge = context.element; + V src = graph.getSource(edge); + V dst = graph.getDest(edge); + + if (src.equals(dst)) { + // Scale the self-loop + Shape baseLoop = loopShape.transform(context); + + Rectangle bounds = baseLoop.getBounds(); + double centerX = bounds.getCenterX(); + double centerY = bounds.getCenterY(); + + AffineTransform at = new AffineTransform(); + at.translate(centerX, centerY); + at.scale(loopScale, loopScale); + at.translate(-centerX, -centerY); + + return at.createTransformedShape(baseLoop); + } else { + return defaultShape.transform(context); + } + } +} diff --git a/reactics-gui/src/pl/umk/mat/martinp/reactics/ContextAutomatonGraph.java b/reactics-gui/src/pl/umk/mat/martinp/reactics/ContextAutomatonGraph.java new file mode 100644 index 0000000..ffd3305 --- /dev/null +++ b/reactics-gui/src/pl/umk/mat/martinp/reactics/ContextAutomatonGraph.java @@ -0,0 +1,438 @@ +package pl.umk.mat.martinp.reactics; + + +import edu.uci.ics.jung.graph.DirectedSparseGraph; +import edu.uci.ics.jung.graph.util.Pair; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import java.awt.*; +import java.awt.geom.Point2D; +import java.io.PrintWriter; +import java.util.*; + + +class ContextAutomatonGraph extends DirectedSparseGraph { + private static final long serialVersionUID = 1L; + + HashMap states = new HashMap<>(); + private CAState initialState = null; + boolean modified = false; + + public ContextAutomatonGraph() { + super(); + } + + public boolean isModified() { return modified; } + + public void markAsModified() { modified = true; } + + public void clearModificationStatus() { modified = false; } + + String toRSSLString() { + StringBuilder rsslString = new StringBuilder("context-automaton {\n"); + + rsslString.append(" states { "); + Collection allStates = states.values(); + Iterator it = allStates.iterator(); + if (it.hasNext()) { + rsslString.append(it.next().getLabel()); + } + while (it.hasNext()) { + rsslString.append(", ").append(it.next().getLabel()); + } + rsslString.append(" };\n"); + + rsslString.append(" init-state { ").append(initialState != null ? initialState.getLabel() : "").append(" };\n"); + rsslString.append(" transitions {\n"); + + for (CAEdge edge : this.getEdges()) { + for (Transition tr : edge.getTransitions()) { + rsslString.append(" { ").append(tr.context).append(" }: "); + rsslString.append(this.getSource(edge).label).append(" -> ").append(this.getDest(edge).label); + + String guard = tr.guard; + + if (guard.length() > 0) { + rsslString.append(" : ").append(guard); + } + + rsslString.append(";\n"); + } + } + rsslString.append(" };\n"); + + rsslString.append("};\n"); + + return rsslString.toString(); + } + + public void exportToXML(PrintWriter output) { + output.println(" "); + + for (CAState state : states.values()) { + Point2D pos = state.getLocation(); + output.print(" "); + } + + for (CAEdge edge : edges.keySet()) { + Pair endPts = this.getEndpoints(edge); + output.print(" "); + + for (Transition tr : edge.getTransitions()) { + output.print(" "); + } + output.println(" "); + } + + output.println(" "); + } + + /** + * Reads the context automaton structure from the XML file. + * + * @param input An XML file containing a valid description of a context automaton. + * @throws ContextAutomatonStructureError In the case of inputFile structure error, eg. \ + * more than one context-automaton section, more than one \ + * initial state, repeating state identifiers, \ + * edge between non-existing nodes, etc. + */ + public void loadFromXML(Document input) throws ContextAutomatonStructureError { + NodeList caSections = input.getElementsByTagName("context-automaton"); + + if (caSections.getLength() == 0 || caSections.getLength() > 1) { + throw new ContextAutomatonStructureError("An XML file should contain a single context automaton description."); + } + + //-------------------------------------------------------------------------------------------------------------- + // Retrieve the states + //-------------------------------------------------------------------------------------------------------------- + + HashMap states = new HashMap(); + NodeList stateList = input.getElementsByTagName("state"); + boolean hasInitialState = false; + + for (int idx = 0; idx < stateList.getLength(); ++idx) { + Node stateNode = stateList.item(idx); + + if (stateNode.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + Element stateElement = (Element) stateNode; + + int id = Integer.parseInt(stateElement.getAttribute("id")); + double x = Double.parseDouble(stateElement.getAttribute("x")); + double y = Double.parseDouble(stateElement.getAttribute("y")); + String name = stateElement.getAttribute("name"); + String initial = stateElement.getAttribute("initial"); + + if (states.containsKey(name)) { + throw new ContextAutomatonStructureError("State name <" + name + "> already used."); + } + + CAState newState = new CAState(id, name, new Point2D.Double(x, y)); + this.addState(newState); + states.put(name, newState); + + if (initial.equals("true")) { + if (hasInitialState) + throw new ContextAutomatonStructureError("There should not be more than one initial state."); + + hasInitialState = true; + this.setInitialState(newState); + } + } + + //-------------------------------------------------------------------------------------------------------------- + // Retrieve the edges + //-------------------------------------------------------------------------------------------------------------- + + NodeList edgeList = input.getElementsByTagName("edge"); + + for (int idx = 0; idx < edgeList.getLength(); ++idx) { + Node edgeNode = edgeList.item(idx); + + if (edgeNode.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + Element edgeElement = (Element) edgeNode; + + String from = edgeElement.getAttribute("from"); + String to = edgeElement.getAttribute("to"); + + // Retrieve all child nodes describing nondeterministic transitions related to this edge + NodeList childNodeList = edgeNode.getChildNodes(); + Vector transitionList = new Vector(); + + for (int cIdx=0; cIdx < childNodeList.getLength(); ++ cIdx) { + Node childNode = childNodeList.item(cIdx); + + if (childNode.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + Element transElement = (Element) childNode; + transitionList.add(new Transition(transElement.getAttribute("context"), transElement.getAttribute("guard"))); + } + + CAState stateFrom = states.get(from); + CAState stateTo = states.get(to); + + if (stateFrom == null || stateTo == null) + throw new ContextAutomatonStructureError("An edge may be created only between existing nodes."); + + this.addEdge(stateFrom, stateTo, transitionList); + } + } + + public Set getReactantsSet() { + Set reactants = new HashSet(); + + for (CAEdge edge : getEdges()) { + for (Transition trans : edge.getTransitions()) { + int start = trans.context.indexOf("{"); + int end = trans.context.indexOf("}"); + + if (start != -1 && end != -1 && start < end) { + reactants.addAll(Arrays.asList(trans.context.substring(start + 1, end).split("\\s*,\\s*"))); + } + } + } + + return reactants; + } + + public void setInitialState(CAState state) { + if (initialState != null) + initialState.setInitial(false); + + state.setInitial(true); + initialState = state; + modified = true; + } + + public CAState getInitialState() { + return initialState; + } + + /** + * Clears the entire automaton structure structure + */ + public void clear() { + for (CAState st : states.values()) + removeVertex(st); + + states.clear(); + CAState.resetIdCounter(); + modified = true; + } + + public boolean addEdge(CAState from, CAState to) { + modified = true; + + return this.addEdge(new CAEdge(), from, to); + } + + public void addEdge(CAState from, CAState to, Collection transitionList) { + CAEdge edge = this.findEdge(from, to); + + if (edge == null) { + CAEdge caEdge = ReactionSystem.getInstance().createCAEdge(); + caEdge.addTransitions(transitionList); + this.addEdge(caEdge, from, to); + } + else + edge.addTransitions(transitionList); + + modified = true; + } + + public void addState(CAState state) throws ContextAutomatonStructureError { + int stateId = state.getId(); + + if (states.get(stateId) != null) + throw new ContextAutomatonStructureError("State id " + stateId + " already in use."); + + states.put(stateId, state); + this.addVertex(state); + modified = true; + } + + public boolean removeState(CAState state) { + if (states.get(state.getId()) != null) { + states.remove(state.getId()); + } + + modified = true; + + // Remove the state with all connected edges + return this.removeVertex(state); + } + +} + + +//------------------------------------------------------------------------------------------------- +// Single node of the context automaton graph +//------------------------------------------------------------------------------------------------- + +class CAState { + public final static Color baseFillColor = Color.lightGray; + public final static Color selectedFillColor = Color.lightGray; + public final static Color initStateFillColor = new Color(102, 178, 255); + + private static final int nodeWidth = 30; + private static final int nodeHeight = 30; + protected static int nextId = 1; + + /** Unique auto incremented identifier of the node */ + protected int id; + + /** User defined label of the node displayed as a tool tip and used in RSSL and XML files */ + protected String label; + + protected Point2D location; + private boolean isInitial = false; + + /** A state created from the description stored in a file. */ + public CAState(int id, String label, Point2D location) { + this.id = id; + this.label = label; + this.location = location; + + // To avoid duplicate node identifiers + if (id >= nextId) + nextId = id + 1; + } + + /** A state created by clicking in graphical component. */ + public CAState(Point2D location) { + this.id = nextId; + this.label = "q" + id; + this.location = location; + ++nextId; + } + + public Point2D getLocation() { return location; } + public void updateLocation(Point2D location) { this.location = location; } + + public boolean isInitial() { return isInitial; } + public void setInitial(boolean status) { isInitial = status; } + + public int getWidth() { return nodeWidth; } + public int getHeight() { return nodeHeight; } + + public String getLabel() { return label; } + public void setLabel(String newLabel) { label = newLabel; } + + public int getId() { return id; } + + static void resetIdCounter() { nextId = 1; } + + public String toString() { return Integer.toString(id); } + + public String getTooltipText() { + return ""+ + "State " + id + "

" + getLabel() + //"
"+ + ""; + } + + Color getFillColor() { + return isInitial ? initStateFillColor : baseFillColor; + } +} + + +//------------------------------------------------------------------------------------------------- +// Single edge of the context automaton graph. +// Each edge can represent a number of single transitions between the same pair of automaton states. +//------------------------------------------------------------------------------------------------- + +class CAEdge { + protected static int nextId = 0; + + protected int id; + + // The list of all nondeterministic transitions represented by this edge + private Vector transitions = new Vector(); + + public boolean hasTransition() { return transitions.size() > 0; } + + + public CAEdge() { + this.id = nextId++; + } + +// public CAEdge(Collection transitionList) { +// this.transitions.addAll(transitionList); +// } + + Vector getTransitions() { + return transitions; + } + + public int getId() { + return id; + } + + public String toString() { + // Update this if you need any label displayed together with graph edge + return ""; + } + + public String getTooltipText() { + StringBuilder tooTip = new StringBuilder("
Context : Guard

"); + + for (Transition tr : transitions) { + tooTip.append(tr.context).append(" : ").append(tr.guard).append("

"); + } + + tooTip.append(""); + + return tooTip.toString(); + } + + public void addTransitions(Collection transitionList) { + transitions.addAll(transitionList); + } +} + + +//------------------------------------------------------------------------------------------------- +// Single transition. To be included in a multiedge. +//------------------------------------------------------------------------------------------------- + +class Transition { + String context; + String guard; + + public Transition(String ctx, String grd) { + context = ctx; + guard = grd; + } +} + + +class ContextAutomatonStructureError extends FileStructureError { + public ContextAutomatonStructureError(String message) { + super(message); + } +} \ No newline at end of file diff --git a/reactics-gui/src/pl/umk/mat/martinp/reactics/CumulatedReactionsViewer.java b/reactics-gui/src/pl/umk/mat/martinp/reactics/CumulatedReactionsViewer.java new file mode 100644 index 0000000..0b32f30 --- /dev/null +++ b/reactics-gui/src/pl/umk/mat/martinp/reactics/CumulatedReactionsViewer.java @@ -0,0 +1,13 @@ +package pl.umk.mat.martinp.reactics; + +import javax.swing.*; +import java.awt.*; + +public class CumulatedReactionsViewer extends JPanel { + + public CumulatedReactionsViewer() { + this.setLayout(new BorderLayout()); + this.add(new JLabel("Cumulate Reactions Viewer", SwingConstants.CENTER), BorderLayout.CENTER); + } + +} diff --git a/reactics-gui/src/pl/umk/mat/martinp/reactics/FormulaEditor.java b/reactics-gui/src/pl/umk/mat/martinp/reactics/FormulaEditor.java new file mode 100644 index 0000000..9ce60e6 --- /dev/null +++ b/reactics-gui/src/pl/umk/mat/martinp/reactics/FormulaEditor.java @@ -0,0 +1,410 @@ +package pl.umk.mat.martinp.reactics; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.EtchedBorder; +import javax.swing.event.TableModelEvent; +import javax.swing.event.TableModelListener; +import javax.swing.table.*; +import java.awt.*; +import java.io.*; +import java.util.Collection; +import java.util.Vector; + + +public class FormulaEditor extends JPanel implements RSObserver { + private FormulaTableModel ftModel; + private JTable formTable; + private ReactionSystem rs; + private Vector formulaList; + + boolean modified = false; + + static final Color TableHeaderBackgroundColor = Color.gray; + static final Color TableHeaderFontColor = Color.white; + static final Color BaseFormulaBackgroundColor = Color.white; + static final Color TrueFormulaBackgroundColor = new Color(138, 242, 183); + static final Color FalseFormulaBackgroundColor = new Color(240, 141, 141); + static final Color InvalidFormulaBackgroundColor = new Color(124, 124, 124); + static final Border emptyBorder = BorderFactory.createEmptyBorder(1, 1, 1, 1); + static final Border selectedBorder = BorderFactory.createLineBorder(Color.RED, 2); + + + public FormulaEditor(ReactionSystem rs) { + this.rs = rs; + formulaList = rs.formulas; + + // Create the table for displaying formulas. Prevent default selection modes. + ftModel = new FormulaTableModel(); + formTable = new JTable(ftModel, new FormulaColumnModel()); + formTable.setCellSelectionEnabled(false); + formTable.setRowSelectionAllowed(true); + + Font headerFont = formTable.getTableHeader().getFont(); + Font cellFont = formTable.getFont(); + JTableHeader formHeader = formTable.getTableHeader(); + formTable.setFont(new Font("Monospaced", Font.PLAIN, cellFont.getSize() + 2)); + formHeader.setFont(new Font(headerFont.getFamily(), Font.BOLD, headerFont.getSize() + 2)); + formHeader.setBackground(TableHeaderBackgroundColor); + formHeader.setForeground(TableHeaderFontColor); + formHeader.setReorderingAllowed(false); + formHeader.setResizingAllowed(true); + formHeader.setBorder(new EtchedBorder(EtchedBorder.LOWERED)); + + FormulaTableCellRenderer renderer = new FormulaTableCellRenderer(); + formTable.setDefaultRenderer(Object.class, renderer); + formTable.createDefaultColumnsFromModel(); + + setLayout(new BorderLayout()); + add(new JScrollPane(formTable), BorderLayout.CENTER); + } + + public boolean isModified() { return modified; } + + public void clearModificationStatus() { modified = false; } + + public String toRSSLString() { + StringBuilder rsslString = new StringBuilder(); + + for (Formula f: formulaList) + rsslString.append(f.toRSSLString()).append("\n"); + + return rsslString.toString(); + } + + public void exportToXML(PrintWriter output) { + output.println(" "); + + for (Formula f : formulaList) + output.println(" " + f.toXMLString()); + + output.println(" "); + } + + public void loadFromXML(Document input) { + formulaList.clear(); + + NodeList formulaTags = input.getElementsByTagName("formula"); + + for (int idx = 0; idx < formulaTags.getLength(); ++idx) { + Node formulaNode = formulaTags.item(idx); + + if (formulaNode.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + Element formulaElement = (Element) formulaNode; + + String label = formulaElement.getAttribute("label"); + String formula = formulaElement.getTextContent(); + formulaList.add(new Formula(label, formula)); + } + + ftModel.fireTableDataChanged(); + } + + private boolean showFormulaEditDialog(JFrame parent, Formula ff) { + final int Select = 0; + final int Approve = 1; + + JTextField labelInput = new JTextField(30); + JTextField formulaInput = new JTextField(30); + + String labelStr = ""; + String formulaStr = ""; + + labelStr = ff.label; + formulaStr = ff.formula; + + labelInput.setToolTipText("A unique label of the formula"); + formulaInput.setToolTipText("Formula to be evaluated"); + + JPanel myPanel = new JPanel(new GridBagLayout()); + GridBagConstraints cs = new GridBagConstraints(); + + cs.insets = new Insets(5, 5, 5, 5); + cs.anchor = GridBagConstraints.WEST; + cs.gridx = 0; + cs.gridy = 0; + myPanel.add(new JLabel("Label"), cs); + + cs.gridx = 1; + cs.gridy = 0; + myPanel.add(labelInput, cs); + + cs.gridx = 0; + cs.gridy = 1; + myPanel.add(new JLabel("Formula"), cs); + + cs.gridx = 1; + cs.gridy = 1; + myPanel.add(formulaInput, cs); + + int opStatus = Select; + + do { + labelInput.setText(labelStr); + formulaInput.setText(formulaStr); + + int result = JOptionPane.showConfirmDialog(parent, myPanel, + "Formula details", JOptionPane.OK_CANCEL_OPTION); + + if (result != JOptionPane.OK_OPTION) { + return false; + } + + labelStr = labelInput.getText(); + formulaStr = formulaInput.getText(); + + ff.label = labelStr; + ff.formula = formulaStr; + + opStatus = Approve; + } + while(opStatus == Select); + + return true; + } + + public void addFormula(JFrame parent) { + Formula ff = new Formula(); + + if (showFormulaEditDialog(parent, ff)) { + formulaList.add(ff); + modified = true; + ftModel.fireTableDataChanged(); + } + } + + // If more than one formula is selected throw an exception only the first can be edited + public void editFormula(JFrame parent) { + int[] selected = formTable.getSelectedRows(); + + if (selected.length != 1) { + JOptionPane.showMessageDialog(parent, "Select a single formula to edit.", "Select formula", JOptionPane.WARNING_MESSAGE); + return; + } + + Formula fEdt = formulaList.get(formTable.convertRowIndexToModel(selected[0])); + + if (fEdt == null) + return; + + showFormulaEditDialog(parent, fEdt); + fEdt.status = Formula.FormulaStatus.None; + modified = true; + ftModel.fireTableDataChanged(); + } + + public void removeFormulas() { + int[] viewRows = formTable.getSelectedRows(); + + // Remove formulas starting from the last to avoid indexes updates + for (int vIdx = viewRows.length - 1; vIdx>=0; --vIdx) { + int mr = formTable.convertRowIndexToModel(viewRows[vIdx]); + formulaList.remove(mr); + } + + modified = true; + ftModel.fireTableDataChanged(); + } + + public Collection getSelectedFormulas() { + Vector selected = new Vector(); + int[] viewRows = formTable.getSelectedRows(); + + for (int vr : viewRows) { + int mr = formTable.convertRowIndexToModel(vr); + selected.add(formulaList.get(mr)); + } + + return selected; + } + + // Needed to repaint JTable. Called after content modification by an external object. + public void refreshStatus() { + ftModel.fireTableDataChanged(); + } + + public void resetFormulasStatus() { + for (Formula ff : formulaList) { + ff.status = Formula.FormulaStatus.None; + ff.mcTime = ff.totalTime = ff.memory = "-"; + } + + ftModel.fireTableDataChanged(); + } + + public void onRSUpdate() { + resetFormulasStatus(); + refreshStatus(); + } + + static class FormulaColumnModel extends DefaultTableColumnModel { + int colno = 0; + + public void addColumn(TableColumn tc) { + switch (colno) { + case 0 -> { + tc.setMinWidth(60); + tc.setMaxWidth(Integer.MAX_VALUE / 10); + tc.setPreferredWidth(100); + } + case 1 -> { + tc.setMinWidth(100); + tc.setMaxWidth(Integer.MAX_VALUE); + } + case 2, 3, 4, 5 -> { + tc.setMinWidth(50); + tc.setPreferredWidth(50); + tc.setMaxWidth(Integer.MAX_VALUE / 10); + } + } + + super.addColumn(tc); + ++colno; + } + } + + + class FormulaTableModel extends AbstractTableModel implements TableModelListener { + private final String[] columnNames = {"Label", "Formula", "Status", "MC Time (s)", "Total Time (s)", "Memory (MB)"}; + + public String getColumnName(int colIdx) { + return columnNames[colIdx]; + } + + public int getRowCount() { + return formulaList.size(); + } + + public int getColumnCount() { + return columnNames.length; + } + + public Object getValueAt(int row, int column) { + Formula ff = formulaList.get(row); + + return switch (column) { + case 0 -> ff.label; + case 1 -> ff.formula; + case 2 -> ff.status; + case 3 -> ff.mcTime; + case 4 -> ff.totalTime; + case 5 -> ff.memory; + default -> ""; + }; + } + + public void setValueAt(Object value, int row, int column) { +// fireTableDataChanged(); + } + + public boolean isCellEditable(int rowIndex, int columnIndex) { return false; } + + public Class getColumnClass(int column) { + return getValueAt(0, column).getClass(); + } + + public void tableChanged(TableModelEvent tableModelEvent) { } + } + + + // To properly display the content of the table containing formulas + class FormulaTableCellRenderer extends JLabel implements TableCellRenderer { + + public FormulaTableCellRenderer() { + // Allows background to be visible + setOpaque(true); + } + + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + String statusStr = ""; + Formula ff = formulaList.get(row); + + setForeground(Color.black); + + if (isSelected) { + setBorder(selectedBorder); + } + else { + setBorder(emptyBorder); + } + + // Set cell background and formula evaluation status + if (column == 2) { + switch (ff.status) { + case None -> { + statusStr = "?"; + setBackground(BaseFormulaBackgroundColor); + } + case True -> { + statusStr = "True"; + setBackground(TrueFormulaBackgroundColor); + } + case False -> { + statusStr = "False"; + setBackground(FalseFormulaBackgroundColor); + } + case Invalid -> { + statusStr = "Invalid"; + setBackground(InvalidFormulaBackgroundColor); + } + } + + setText(statusStr); + } + else { + setText((String) value); + setBackground(BaseFormulaBackgroundColor); + } + + if (column > 2) { + setHorizontalAlignment(SwingConstants.RIGHT); + } else { + setHorizontalAlignment(SwingConstants.LEFT); + } + + return this; + } + } +} + + +class Formula { + public enum FormulaStatus {None, True, False, Invalid}; + + String label; + String formula; + FormulaStatus status; + String mcTime; + String totalTime; + String memory; + + public Formula() { + this("", ""); + } + + public Formula(String label, String formula) { + this.label = label; + this.formula = formula; + this.status = FormulaStatus.None; + this.mcTime = this.totalTime = this.memory = "-"; + } + + public String toRSSLString() { + return "rsctlk-property { " + label + " : " + formula + " };"; + } + + public String toXMLString() { + return "" + + formula.replaceAll("<", "<").replaceAll(">", ">") + + ""; + } +} + diff --git a/reactics-gui/src/pl/umk/mat/martinp/reactics/HelpWindow.java b/reactics-gui/src/pl/umk/mat/martinp/reactics/HelpWindow.java new file mode 100644 index 0000000..9e970cf --- /dev/null +++ b/reactics-gui/src/pl/umk/mat/martinp/reactics/HelpWindow.java @@ -0,0 +1,125 @@ +package pl.umk.mat.martinp.reactics; + +import javax.swing.*; +import javax.swing.border.*; +import javax.swing.event.*; +import java.awt.*; +import java.awt.event.*; +import java.net.*; + +public class HelpWindow extends JFrame { + private static final long serialVersionUID = 327019113533268901L; + private static final String startDocument = "index.html"; + private static final String docPath = "/help/"; + + private static HelpWindow instance = null; + private static JTextPane helpPane = new JTextPane();; + + public static synchronized HelpWindow getInstance() { + if (instance == null) + instance = new HelpWindow(); + + return instance; + } + + private HelpWindow() { + super("ReactICS GUI Help"); + + getContentPane().setLayout(new BorderLayout()); + + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + EtchedBorder panelBorder = new EtchedBorder(EtchedBorder.LOWERED); + mainPanel.setBorder(panelBorder); + + helpPane.setEditable(false); + helpPane.addHyperlinkListener(new HyperlinkListener() { + public void hyperlinkUpdate(HyperlinkEvent hle) { + if (hle.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + String target = hle.getDescription(); + + if (target.startsWith("#")) { + helpPane.scrollToReference(target.substring(1)); + } + } + } + }); + + try { + URL helpUrl = this.getClass().getResource(docPath + startDocument); + helpPane.setPage(helpUrl); + + helpPane.revalidate(); + helpPane.repaint(); + } + catch (Exception e) { + System.out.println("Exception: " + e.getMessage()); + JOptionPane.showMessageDialog(null, "Can not load help content", + "Help", JOptionPane.ERROR_MESSAGE); + } + + JScrollPane helpScroller = new JScrollPane(); + JViewport helpVp = new JViewport(); + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + helpVp.setPreferredSize(new Dimension(screenSize.width/2, screenSize.height-200)); + helpVp.add(helpPane); + helpScroller.setViewport(helpVp); + mainPanel.add(helpScroller, BorderLayout.CENTER); + getContentPane().add(mainPanel, BorderLayout.CENTER); + + pack(); + } + + public void show(Component parent) { + setLocationRelativeTo(parent); + setVisible(true); + } + +} + + +class AboutWindow extends JFrame { + private static AboutWindow instance = null; + + public static synchronized AboutWindow getInstance() { + if (instance == null) + instance = new AboutWindow(); + + return instance; + } + + private AboutWindow() { + super("About ..."); + + getContentPane().setLayout(new BorderLayout()); + + JPanel infoPanel = new JPanel(); + infoPanel.add(new JLabel("

" + ReacticsGUI.ApplicationName + "

" + + "

version " + ReacticsGUI.ApplicationVersion + " (" + ReacticsGUI.ApplicationReleaseDate + ")

" + + "

Model Checker for Reaction Systems

" + + "

ReactICS Research Team (CC BY 4.0)

" + + "

https://reactics.org

")); + getContentPane().add(infoPanel, BorderLayout.CENTER); + + JButton closeButton = new JButton("Close"); + closeButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + setVisible(false); + } + }); + JPanel ctrlPanel = new JPanel(); + ctrlPanel.add(closeButton); + ctrlPanel.setAlignmentX(SwingConstants.RIGHT); + getContentPane().add(ctrlPanel, BorderLayout.SOUTH); + + setSize(new Dimension(400, 250)); + setResizable(false); + this.setAlwaysOnTop(true); + this.setUndecorated(true); + } + + public void show(Component parent) { + setLocationRelativeTo(parent); + setVisible(true); + } +} \ No newline at end of file diff --git a/reactics-gui/src/pl/umk/mat/martinp/reactics/ProcessEditor.java b/reactics-gui/src/pl/umk/mat/martinp/reactics/ProcessEditor.java new file mode 100644 index 0000000..8ace501 --- /dev/null +++ b/reactics-gui/src/pl/umk/mat/martinp/reactics/ProcessEditor.java @@ -0,0 +1,427 @@ +package pl.umk.mat.martinp.reactics; + +import javax.swing.*; +import javax.swing.border.*; +import javax.swing.table.AbstractTableModel; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.PrintWriter; +import java.util.*; + + +//------------------------------------------------------------------------------------------------- +// Graphical component for editing a single process (list of reactions) +//------------------------------------------------------------------------------------------------- + +class ProcessEditor extends JPanel { + private JTable reactionList; + private RSTableModel reactionsModel; + + private RSProcess rsProcess; + private boolean modified = false; + + private JCheckBox selectBox; + private TitledBorder outBorder; + private ReactionSystem rs; + + private final Color borderColor = new Color(24, 104, 92);; + private final Color selectedBorderColor = new Color(150, 10, 10); + private static final int borderThickness = 4; + + public ProcessEditor(RSProcess rsProc) { + rsProcess = rsProc; + init(); + } + + public RSProcess getProcess() { return rsProcess; } + + public boolean isSelected() { + return selectBox.isSelected(); + } + + public boolean isModified() { return modified; } + + public void clearModificationStatus() { modified = false; } + + public String getLabel() { + return rsProcess.label; + } + + private void init() { + // Create border for the component + outBorder = BorderFactory.createTitledBorder(rsProcess.label); + Font font = outBorder.getTitleFont(); + Font newFont = new Font (font.getFamily(), Font.BOLD, font.getSize() + 2); + outBorder.setTitleFont(new Font (font.getFamily(), Font.BOLD, font.getSize() + 2)); + outBorder.setTitleColor(borderColor); + outBorder.setBorder(new LineBorder(borderColor, borderThickness, true)); + setBorder(outBorder); + + // Create reactions list component + reactionsModel = new RSTableModel(); + reactionList = new JTable(reactionsModel); + + Font cellFont = reactionList.getFont(); + Font headerFont = reactionList.getTableHeader().getFont(); + + reactionList.setFont(new Font("Monospaced", 0, cellFont.getSize() + 2)); + reactionList.getTableHeader().setFont(new Font(headerFont.getFamily(), Font.BOLD, headerFont.getSize() + 2)); + reactionList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + JPanel buttonPanel = new JPanel(); + + JButton addButton = new JButton("Add reaction"); + addButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { addReaction(); } + }); + buttonPanel.add(addButton); + + JButton edtButton = new JButton("Edit reaction"); + edtButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + editReaction(); + } + }); + buttonPanel.add(edtButton); + + JButton rmButton = new JButton("Remove reaction"); + rmButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + removeReactions(); + } + }); + buttonPanel.add(rmButton); + + selectBox = new JCheckBox("Select"); + selectBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + toggleSelection(); + } + }); + buttonPanel.add(selectBox); + + add(buttonPanel); + + add(new JScrollPane(reactionList)); + + setPreferredSize(new Dimension(-1, 200)); + + rs = ReactionSystem.getInstance(); + } + + private boolean showReactionEditDialog(JComponent parent, Reaction rr) { + final int Select = 0; + final int Approve = 1; + + JTextField reactantsInput = new JTextField(30); + JTextField inhibitorsInput = new JTextField(30); + JTextField productsInput = new JTextField(30); + + String reactantsStr = ""; + String inhibitorsStr = ""; + String productsStr = ""; + + Iterator it = rr.reactants.iterator(); + StringBuilder dataStr = new StringBuilder(); + + while (it.hasNext()) + dataStr.append(it.next()).append(" "); + + reactantsStr = dataStr.toString(); + + it = rr.inhibitors.iterator(); + dataStr.setLength(0); + + while (it.hasNext()) + dataStr.append(it.next()).append(" "); + + inhibitorsStr = dataStr.toString(); + + it = rr.products.iterator(); + dataStr.setLength(0); + + while (it.hasNext()) + dataStr.append(it.next()).append(" "); + + productsStr = dataStr.toString(); + + reactantsInput.setToolTipText("Space separated list of reactants"); + inhibitorsInput.setToolTipText("Space separated list of inhibitors"); + productsInput.setToolTipText("Space separated list of products"); + + JPanel myPanel = new JPanel(new GridBagLayout()); + GridBagConstraints cs = new GridBagConstraints(); + + cs.insets = new Insets(5, 5, 5, 5); + cs.anchor = GridBagConstraints.WEST; + cs.gridx = 0; + cs.gridy = 0; + myPanel.add(new JLabel("Reactants"), cs); + + cs.gridx = 1; + cs.gridy = 0; + myPanel.add(reactantsInput, cs); + + cs.gridx = 0; + cs.gridy = 1; + myPanel.add(new JLabel("Inhibitors"), cs); + + cs.gridx = 1; + cs.gridy = 1; + myPanel.add(inhibitorsInput, cs); + + cs.gridx = 0; + cs.gridy = 2; + myPanel.add(new JLabel("Products"), cs); + + cs.gridx = 1; + cs.gridy = 2; + myPanel.add(productsInput, cs); + + int opStatus = Select; + + do { + reactantsInput.setText(reactantsStr); + inhibitorsInput.setText(inhibitorsStr); + productsInput.setText(productsStr); + + int result = JOptionPane.showConfirmDialog(parent, myPanel, + "Reaction details", JOptionPane.OK_CANCEL_OPTION); + + if (result != JOptionPane.OK_OPTION) { + return false; + } + + reactantsStr = reactantsInput.getText(); + inhibitorsStr = inhibitorsInput.getText(); + productsStr = productsInput.getText(); + + HashSet reactantsSet = new HashSet(Arrays.asList(reactantsStr.trim().split("\\s+"))); + HashSet inhibitorsSet = new HashSet(Arrays.asList(inhibitorsStr.trim().split("\\s+"))); + HashSet productsSet = new HashSet(Arrays.asList(productsStr.trim().split("\\s+"))); + + // Verify that reactant and inhibitor sets have empty intersection + boolean intersection = false; + + for (String entity : inhibitorsSet) { + if (reactantsSet.contains(entity)) { + intersection = true; + break; + } + } + + if (intersection) { + JOptionPane.showMessageDialog(this, "Reactant and inhibitor sets have to be disjoint.", "Error", JOptionPane.ERROR_MESSAGE); + continue; + } + + rr.reactants.clear(); + rr.reactants.addAll(reactantsSet); + rr.inhibitors.clear(); + rr.inhibitors.addAll(inhibitorsSet); + rr.products.clear(); + rr.products.addAll(productsSet); + + opStatus = Approve; + } + while(opStatus == Select); + + return true; + } + + public Set getReactantsSet() { + HashSet rset = new HashSet(); + + for (Reaction rr : rsProcess.reactions) + rset.addAll(rr.getReactantsSet()); + + return rset; + } + + public void exportToXML(PrintWriter output) { + output.println(" "); + + for (Reaction rr : rsProcess.reactions) + rr.exportToXML(output); + + output.println(" "); + } + + public String toRSSLString() { + StringBuilder rsslString = new StringBuilder(" " + rsProcess.label + " {\n"); + + for (Reaction rr : rsProcess.reactions) { + rsslString.append(rr.toRSSLString()); + } + + rsslString.append(" };\n"); + + return rsslString.toString(); + } + + void rename(String newLabel) { + rsProcess.label = newLabel; + outBorder.setTitle(rsProcess.label); + + this.repaint(); + } + + // Add new reaction + public void addReaction(Reaction rr) { + rsProcess.reactions.add(rr); + reactionsModel.fireTableDataChanged(); + } + + private void addReaction() { + Reaction rr = new Reaction(); + if (!showReactionEditDialog(this, rr)) + return; + + rsProcess.reactions.add(rr); + modified = true; + reactionsModel.fireTableDataChanged(); + rs.notifyObservers(); + } + + // Edit details of an existing reaction + private void editReaction() { + int rIdx = reactionList.getSelectedRow(); + + if (rIdx == -1) { + JOptionPane.showMessageDialog(this, "Select the reaction to edit.", "Select reaction", JOptionPane.WARNING_MESSAGE); + return; + } + + Reaction rr = new Reaction(rsProcess.reactions.get(rIdx)); + reactionList.clearSelection(); + if (!showReactionEditDialog(this, rr)) { + return; + } + + rsProcess.reactions.setElementAt(rr, rIdx); + modified = true; + reactionsModel.fireTableDataChanged(); + rs.notifyObservers(); + } + + // Remove an existing reaction + private void removeReactions() { + int rIdx = reactionList.getSelectedRow(); + + if (rIdx == -1) { + JOptionPane.showMessageDialog(this, "Select the reaction to be removed.", "Select reaction", JOptionPane.WARNING_MESSAGE); + return; + } + + rsProcess.reactions.remove(rIdx); + reactionList.clearSelection(); + reactionsModel.fireTableDataChanged(); + modified = true; + this.repaint(); + rs.notifyObservers(); + } + + private void toggleSelection() { + if (selectBox.isSelected()) { + outBorder.setTitleColor(selectedBorderColor); + outBorder.setBorder(new LineBorder(selectedBorderColor, borderThickness, true)); + } + else { + outBorder.setTitleColor(borderColor); + outBorder.setBorder(new LineBorder(borderColor, borderThickness, true)); + } + + this.repaint(); + } + + class RSTableModel extends AbstractTableModel { + + private String columnNames[] = {"Reactants", "Inhibitors", "Results"}; + private Vector[] data; + + public String getColumnName(int colIdx) { + return columnNames[colIdx]; + } + + public int getRowCount() { + return rsProcess.reactions.size(); + } + + public int getColumnCount() { + return columnNames.length; + } + + public Object getValueAt(int row, int column) { + Reaction rr = rsProcess.reactions.get(row); + + return switch (column) { + case 0 -> rr.reactants; + case 1 -> rr.inhibitors; + case 2 -> rr.products; + default -> ""; + }; + } + + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + } + +} + + +//------------------------------------------------------------------------------------------------- +// Object representing a single reaction +//------------------------------------------------------------------------------------------------- + +class Reaction { + LinkedList reactants; + LinkedList inhibitors; + LinkedList products; + + public Reaction() { + reactants = new LinkedList(); + inhibitors = new LinkedList(); + products = new LinkedList(); + } + + public Reaction(Reaction rr) { + reactants = new LinkedList(rr.reactants); + inhibitors = new LinkedList(rr.inhibitors); + products = new LinkedList(rr.products); + } + + public Set getReactantsSet() { + Set rset = new HashSet(); + rset.addAll(reactants); + rset.addAll(inhibitors); + rset.addAll(products); + + return rset; + } + + public String toRSSLString() { + return " {" + "{" + reactants.toString().replace("[", "").replace("]", "") + "}, " + + "{" + inhibitors.toString().replace("[", "").replace("]", "") + "} -> " + + "{" + products.toString().replace("[", "").replace("]", "") + "}" + + "};\n"; + } + + public void exportToXML(PrintWriter output) { + output.println(" "); + + for (String str : reactants) + output.println(" " + str + ""); + + for (String str : inhibitors) + output.println(" " + str + ""); + + for (String str : products) + output.println(" " + str + ""); + + output.println(" "); + } +} diff --git a/reactics-gui/src/pl/umk/mat/martinp/reactics/ReactantSetPanel.java b/reactics-gui/src/pl/umk/mat/martinp/reactics/ReactantSetPanel.java new file mode 100644 index 0000000..14a46e1 --- /dev/null +++ b/reactics-gui/src/pl/umk/mat/martinp/reactics/ReactantSetPanel.java @@ -0,0 +1,49 @@ +package pl.umk.mat.martinp.reactics; + +import javax.swing.*; +import javax.swing.border.LineBorder; +import javax.swing.border.TitledBorder; +import java.awt.*; +import java.util.*; +import java.util.stream.Collectors; + + +public class ReactantSetPanel extends JPanel implements RSObserver { + private final int borderThickness = 2; + + JTextArea textArea = new JTextArea(); + + public ReactantSetPanel() { + textArea.setEditable(false); + textArea.setLineWrap(true); + textArea.setWrapStyleWord(false); + Font textFont = textArea.getFont(); + textArea.setFont(new Font(textFont.getFamily(), Font.BOLD, textFont.getSize()+2)); + + this.setPreferredSize(new Dimension(-1, 75)); + + this.setLayout(new BorderLayout()); + this.add(new JScrollPane(textArea), BorderLayout.CENTER); + + TitledBorder outBorder = BorderFactory.createTitledBorder("Reactants"); + Font borderFont = outBorder.getTitleFont(); + outBorder.setTitleFont(new Font (borderFont.getFamily(), Font.BOLD, borderFont.getSize()+2)); + outBorder.setBorder(new LineBorder(Color.black, borderThickness, true)); + setBorder(outBorder); + } + + public void updateReactants(Set reactantsSet) { + textArea.setText(""); +// textArea.setText(Arrays.toString(reactantsSet.toArray())); + textArea.setText(Arrays.stream(reactantsSet.toArray()).map(String::valueOf).collect(Collectors.joining(" "))); + } + + public void clear() { + textArea.setText(""); + } + + public void onRSUpdate() { + ReactionSystem rs = ReactionSystem.getInstance(); + updateReactants(rs.getReactants()); + } +} \ No newline at end of file diff --git a/reactics-gui/src/pl/umk/mat/martinp/reactics/ReacticsGUI.java b/reactics-gui/src/pl/umk/mat/martinp/reactics/ReacticsGUI.java new file mode 100644 index 0000000..10d126e --- /dev/null +++ b/reactics-gui/src/pl/umk/mat/martinp/reactics/ReacticsGUI.java @@ -0,0 +1,791 @@ +/** Main application window */ + +package pl.umk.mat.martinp.reactics; + +import org.w3c.dom.Document; +import org.xml.sax.SAXException; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Collection; +import java.util.TreeSet; + + +public class ReacticsGUI extends JFrame { + public final static String ApplicationName = "ReactICS GUI"; + public final static String ApplicationVersion = "0.1"; + public final static String ApplicationReleaseDate = "2025"; + + private static final String defaultConfigFileName = "reactics.conf"; + + private JFrame window; + private JTabbedPane modulePane; + private ReactantSetPanel reactantSetPanel; + private ReactionSystemEditor reactionSystemEditor; + private ContextAutomatonEditor contextAutomatonEditor; + private TransitionSystemViewer transitionSystemViewer; + private TransitionSystemViewer compressedTransitionSystemViewer; + private FormulaEditor formulaEditor; + + private FileSelector fileSelector; + + private ReactionSystem reactionSystem; + private ReacticsRuntime reactics; + + + public ReacticsGUI() { + super(ApplicationName); + + reactionSystem = ReactionSystem.getInstance(); + + try { + reactics = ReacticsRuntime.getInstance(); + } + catch (ReacticsRuntimeException rre) { + JOptionPane.showMessageDialog(this, + rre.getMessage(), + "Reactics Runtime Error", JOptionPane.ERROR_MESSAGE); + } + + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + setSize(new Dimension(screenSize.width-200, screenSize.height-200)); + createMenuBar(); + createContentPane(); + + fileSelector = FileSelector.getInstance(); + + try { + reactics.loadConfig(defaultConfigFileName); + } + catch (ConfigReadingError cre) { + System.err.println("[ReactICS] Error reading configuration: " + cre.getMessage()); + JOptionPane.showMessageDialog(this, + "Could not read configuration\n" + cre.getMessage(), + "Reactics Runtime Error", JOptionPane.ERROR_MESSAGE); + } + catch (InvalidRuntimeException ire) { + System.err.println("[ReactICS] ReactICS runtime cannot be executed" + ire.getMessage()); + JOptionPane.showMessageDialog(this, + "Invalid ReactICS runtime patn\n" + ire.getMessage() + + "\nYou may proceed with editing reaction system structure," + + "however not model checking will be possible.", + "Reactics Runtime Error", JOptionPane.ERROR_MESSAGE); + } + + window = this; + } + + private void createMenuBar() { + JMenuBar menuBar = new JMenuBar(); + + JMenu fileMenu = new JMenu("File"); + menuBar.add(fileMenu); + + JMenuItem xmlLoadItem = new JMenuItem("Load from XML file"); + xmlLoadItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + loadFromXML(); + } + }); + fileMenu.add(xmlLoadItem); + + JMenuItem xmlsaveItem = new JMenuItem("Save to XML file"); + xmlsaveItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + saveAsXML(); + } + }); + fileMenu.add(xmlsaveItem); + + JMenuItem rsslImportItem = new JMenuItem("Import from RSSL file"); + rsslImportItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + importFromRSSL(); + } + }); + fileMenu.add(rsslImportItem); + + JMenuItem rsslExportItem = new JMenuItem("Save to RSSL file"); + rsslExportItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + exportToRSSLFile(); + } + }); + fileMenu.add(rsslExportItem); + + JMenuItem isplExportItem = new JMenuItem("Export to ISPL file"); + isplExportItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { exportToISPLFile(); } + }); + fileMenu.add(isplExportItem); + + JMenuItem exitItem = new JMenuItem("Exit"); + exitItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + System.exit(0); + } + }); + fileMenu.addSeparator(); + fileMenu.add(exitItem); + + JMenu helpMenu = new JMenu("Help"); + menuBar.add(helpMenu); + + JMenuItem helpItem = new JMenuItem("Help"); + helpItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { showHelpWindow(); } + }); + helpMenu.add(helpItem); + + JMenuItem aboutItem = new JMenuItem("About"); + aboutItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { showAboutWindow(); } + }); + helpMenu.add(aboutItem); + + setJMenuBar(menuBar); + } + + private void createContentPane() { + modulePane = new JTabbedPane(); + modulePane.setTabPlacement(JTabbedPane.NORTH); + + //----------------------------------------------------------------------------------------- + // Reaction System Editor + //----------------------------------------------------------------------------------------- + + reactionSystemEditor = new ReactionSystemEditor(); + modulePane.addTab("Reaction System", reactionSystemEditor); + reactionSystem.addObserver(reactionSystemEditor); + + + //----------------------------------------------------------------------------------------- + // Context Automaton Editor + //----------------------------------------------------------------------------------------- + + contextAutomatonEditor = new ContextAutomatonEditor(getSize()); + modulePane.addTab("Context Automaton", contextAutomatonEditor); + reactionSystem.addObserver(contextAutomatonEditor); + + + //----------------------------------------------------------------------------------------- + // Transition System Viewer + //----------------------------------------------------------------------------------------- + + JPanel transitionSystemPanel = new JPanel(new BorderLayout()); + transitionSystemViewer = new TransitionSystemViewer(getSize(), false); + transitionSystemPanel.add(transitionSystemViewer, BorderLayout.CENTER); + reactionSystem.addObserver(transitionSystemViewer); + + JPanel tsCtrlPanel = new JPanel(); + JButton tsUpdateBtn = new JButton("Update transition system"); + tsUpdateBtn.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { updateTransitionSystem(); } + }); + tsCtrlPanel.add(tsUpdateBtn); + + JCheckBox tsLockCBox = new JCheckBox("Lock relative nodes positions"); + tsLockCBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + transitionSystemViewer.lockNodesPositions(tsLockCBox.isSelected()); + } + }); + tsCtrlPanel.add(tsLockCBox); + + transitionSystemPanel.add(tsCtrlPanel, BorderLayout.NORTH); + modulePane.addTab("Transition System", transitionSystemPanel); + + //----------------------------------------------------------------------------------------- + // Compressed Transition System Viewer + //----------------------------------------------------------------------------------------- + + JPanel compressedTransitionSystemPanel = new JPanel(new BorderLayout()); + compressedTransitionSystemViewer = new TransitionSystemViewer(getSize(), true); + compressedTransitionSystemPanel.add(compressedTransitionSystemViewer, BorderLayout.CENTER); + reactionSystem.addObserver(compressedTransitionSystemViewer); + + JPanel ctsCtrlPanel = new JPanel(); + JButton ctsUpdateBtn = new JButton("Update transition system"); + ctsUpdateBtn.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { updateTransitionSystem(); } + }); + ctsCtrlPanel.add(ctsUpdateBtn); + + JCheckBox ctsLockCBox = new JCheckBox("Lock relative nodes positions"); + ctsLockCBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + compressedTransitionSystemViewer.lockNodesPositions(ctsLockCBox.isSelected()); + } + }); + ctsCtrlPanel.add(ctsLockCBox); + + compressedTransitionSystemPanel.add(ctsCtrlPanel, BorderLayout.NORTH); + modulePane.addTab("Compressed Transition System", compressedTransitionSystemPanel); + + //----------------------------------------------------------------------------------------- + // Formula Editor + //----------------------------------------------------------------------------------------- + + JPanel formulaEditorPanel = new JPanel(); + formulaEditorPanel.setLayout(new BorderLayout()); + formulaEditor = new FormulaEditor(reactionSystem); + reactionSystem.addObserver(formulaEditor); + formulaEditorPanel.add(formulaEditor, BorderLayout.CENTER); + + JPanel formulaCtrlPanel = new JPanel(); + JButton addButton = new JButton("Add formula"); + addButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { formulaEditor.addFormula(window); } + }); + formulaCtrlPanel.add(addButton); + + JButton edtButton = new JButton("Edit formula"); + edtButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + formulaEditor.editFormula(window); + } + }); + formulaCtrlPanel.add(edtButton); + + JButton rmButton = new JButton("Remove formulas"); + rmButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + formulaEditor.removeFormulas(); + } + }); + formulaCtrlPanel.add(rmButton); + + JButton evalButton = new JButton("Evaluate"); + evalButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + evaluateFormulas(); + } + }); + formulaCtrlPanel.add(evalButton); + + JButton resetButton = new JButton("Reset"); + resetButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + formulaEditor.resetFormulasStatus(); + } + }); + formulaCtrlPanel.add(resetButton); + + formulaEditorPanel.add(formulaCtrlPanel, BorderLayout.NORTH); + + //----------------------------------------------------------------------------------------- + // Split the main window into two parts. The upper one contains details of the reaction + // system. The bottom part contains the list of formulas for model checking. + //----------------------------------------------------------------------------------------- + + JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, modulePane, formulaEditorPanel); + splitPane.setResizeWeight(0.75); +// splitPane.setOneTouchExpandable(true); + splitPane.setDividerSize(8); + + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(splitPane, BorderLayout.CENTER); + + //----------------------------------------------------------------------------------------- + // Update button + Reactant Set Viewer + //----------------------------------------------------------------------------------------- + + JPanel topPanel = new JPanel(); + topPanel.setLayout(new BorderLayout()); + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.Y_AXIS)); + + JButton updateButton = new JButton("
Update
Reaction System
"); + updateButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + updateReactionSystem(); + } + }); + buttonPanel.add(updateButton); + topPanel.add(buttonPanel, BorderLayout.WEST); + reactantSetPanel = new ReactantSetPanel(); + reactionSystem.addObserver(reactantSetPanel); + topPanel.add(reactantSetPanel, BorderLayout.CENTER); + getContentPane().add(topPanel, BorderLayout.NORTH); + } + + private boolean isModified() { + return reactionSystemEditor.isModified() || contextAutomatonEditor.isModified(); + } + + private void clearModificationStatus() { + reactionSystemEditor.clearModificationStatus(); + contextAutomatonEditor.clearModificationStatus(); + transitionSystemViewer.clearModificationStatus(); + compressedTransitionSystemViewer.clearModificationStatus(); + } + + private void updateReactionSystem() { + System.out.println("[ReactICS] Update reaction system structure"); + + Path rsslFile = reactics.getRSSLFile(); + + try { + Files.writeString(rsslFile, ""); + + PrintWriter rsslStream = new PrintWriter(Files.newBufferedWriter(rsslFile)); + printRSStructure(rsslStream); + rsslStream.close(); + } + catch (IOException ie) { + System.err.println("[ReactICS] Could not read transition system structure: " + ie.getMessage()); + + JOptionPane.showMessageDialog(this, + "Could not read transition system structure\n" + ie.getMessage(), + "Error", JOptionPane.ERROR_MESSAGE); + } + + reactionSystem.notifyObservers(); + updateReactantSet(); + clearModificationStatus(); + } + + private void updateReactantSet() { + TreeSet rset = new TreeSet(); + + rset.addAll(reactionSystemEditor.getReactantsSet()); + rset.addAll(contextAutomatonEditor.getReactantsSet()); + rset.remove(""); + reactantSetPanel.updateReactants(rset); + } + + private void updateTransitionSystem() { + if (isModified()) { + updateReactionSystem(); + } + else if (transitionSystemViewer.isComputed()) { + // Skip re-computing transition system structure + return; + } + + try { + String tsStructure = reactics.getTransitionSystemStructure(); + transitionSystemViewer.updateTransitionSystemStructure(tsStructure); + compressedTransitionSystemViewer.updateTransitionSystemStructure(tsStructure); + } + catch (IOException ioe) { + System.out.println("[ReactICS] I/O Error: " + ioe.getMessage()); + JOptionPane.showMessageDialog(this, + "Could not write reaction system structure to a temporary file\n" + ioe.getMessage(), + "Error", JOptionPane.ERROR_MESSAGE); + } + catch (ReacticsRuntimeException rre) { + System.out.println("[ReactICS] Runtime Error: " + rre.getMessage()); + JOptionPane.showMessageDialog(this, + rre.getMessage(), + "Reactics Runtime Error", JOptionPane.ERROR_MESSAGE); + } + } + + private void evaluateFormulas() { + Collection formulas = formulaEditor.getSelectedFormulas(); + + if (isModified()) + updateReactionSystem(); + + Path rsslFile = reactics.getRSSLFile(); + long originalSize = -1; + + try { + originalSize = Files.size(rsslFile); + } + catch (IOException ioe) { + System.err.println("[ReactICS] Error: " + ioe.getMessage()); + JOptionPane.showMessageDialog(this, ioe.getMessage(), + "Error", JOptionPane.ERROR_MESSAGE); + } + + for (Formula ff : formulas) { + try { + System.out.println("[ReactICS] Evaluate formula: " + ff.label); + + Files.writeString(rsslFile, ff.toRSSLString() + "\n", StandardCharsets.UTF_8, StandardOpenOption.APPEND); + + ReacticsRuntime.EvalResult result = reactics.evaluateFormula(ff.label); + ff.status = result.result(); + ff.mcTime = result.mcTime() + " s"; + ff.totalTime = result.totalTime() + " s"; + ff.memory = result.memory() + " MB"; + formulaEditor.refreshStatus(); + } + catch(IOException ioe) { + System.err.println("[ReactICS] Error: " + ioe.getMessage()); + JOptionPane.showMessageDialog(this, ioe.getMessage(), + "Error", JOptionPane.ERROR_MESSAGE); + } + catch(ReacticsRuntimeException rre) { + System.err.println("[ReactICS] Runtime error: " + rre.getMessage()); + JOptionPane.showMessageDialog(this, + rre.getMessage(), + "Reactics Runtime Error", JOptionPane.ERROR_MESSAGE); + + ff.status = Formula.FormulaStatus.Invalid; + } + finally { + try { + RandomAccessFile raf = new RandomAccessFile(rsslFile.toFile(), "rw"); + raf.setLength(originalSize); + } + catch (IOException ioe) { + System.err.println("[ReactICS] Error: " + ioe.getMessage()); + JOptionPane.showMessageDialog(this, ioe.getMessage(), + "Error", JOptionPane.ERROR_MESSAGE); + } + } + } + } + + private void saveAsXML() { + File outFile = fileSelector.selectOutputFile(this, new File(System.getProperty("user.dir")), + new FileNameExtensionFilter("Reaction System (XML)", "xml")); + + if(outFile == null) + return; + + System.out.println("[ReactICS] Save to XML file: " + outFile.getName()); + + try { + PrintWriter xmlOut = new PrintWriter(new FileWriter(outFile)); + xmlOut.println(""); + reactionSystemEditor.exportToXML(xmlOut); + contextAutomatonEditor.exportToXML(xmlOut); + formulaEditor.exportToXML(xmlOut); + xmlOut.println(""); + xmlOut.flush(); + xmlOut.close(); + } + catch(IOException ioe) { + JOptionPane.showMessageDialog(this, ioe.getMessage(), + "Error", JOptionPane.ERROR_MESSAGE); + } + } + + private void loadFromXML() { + FileNameExtensionFilter fileTypeFilter = new FileNameExtensionFilter("Reaction System (XML)", "xml"); + JFileChooser fc = new JFileChooser(); + fc.addChoosableFileFilter(fileTypeFilter); + fc.setAcceptAllFileFilterUsed(true); + fc.setCurrentDirectory(new File(System.getProperty("user.dir"))); + + + if(fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { + File xmlFile = fc.getSelectedFile(); + loadFromXMLFile(xmlFile); + setTitle("ReactICS GUI : " + xmlFile.getName()); + } + + modulePane.setSelectedIndex(0); + clearModificationStatus(); + } + + + private void loadFromXMLFile(File xmlFile) { + reactionSystemEditor.clear(); + contextAutomatonEditor.clear(); + reactantSetPanel.clear(); + transitionSystemViewer.clear(); + compressedTransitionSystemViewer.clear(); + + try { + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + Document doc = dBuilder.parse(xmlFile); + doc.getDocumentElement().normalize(); + reactionSystemEditor.loadFromXML(doc); + contextAutomatonEditor.loadFromXML(doc); + formulaEditor.loadFromXML(doc); + updateReactionSystem(); + } + catch(IOException ioe) { + System.err.println("[ReactICS] Error: " + ioe.getMessage()); + + JOptionPane.showMessageDialog(this, "Error reading file: " + xmlFile.getName() + "\n" + ioe.getMessage(), + "Input error", JOptionPane.ERROR_MESSAGE); + + reactionSystemEditor.clear(); + contextAutomatonEditor.clear(); + reactantSetPanel.clear(); + } + catch(ParserConfigurationException pce) { + System.err.println("[ReactICS] Parse Configuration Error: " + pce.getMessage()); + + JOptionPane.showMessageDialog(this, pce.getMessage(), + "Parse Configuration Error", JOptionPane.ERROR_MESSAGE); + + reactionSystemEditor.clear(); + contextAutomatonEditor.clear(); + reactantSetPanel.clear(); + } + catch(SAXException se) { + System.err.println("[ReactICS] SAXException: " + se.getMessage()); + + JOptionPane.showMessageDialog(this, se.getMessage(), + "Parse error", JOptionPane.ERROR_MESSAGE); + + reactionSystemEditor.clear(); + contextAutomatonEditor.clear(); + reactantSetPanel.clear(); + } + catch(FileStructureError err) { + System.err.println("[ReactICS]: File Structure Error: " + err.getMessage()); + + JOptionPane.showMessageDialog(this, err.getMessage(), + "XML file structure error", JOptionPane.ERROR_MESSAGE); + + reactionSystemEditor.clear(); + contextAutomatonEditor.clear(); + reactantSetPanel.clear(); + } + catch(Exception e) { + System.out.println("[ReactICS] Error: " + e.getMessage()); + + JOptionPane.showMessageDialog(this, "Unexpected error reading file: " + xmlFile.getName() + + " " + e.getMessage(), + "Input error", JOptionPane.ERROR_MESSAGE); + + reactionSystemEditor.clear(); + contextAutomatonEditor.clear(); + reactantSetPanel.clear(); + } + } + + + private void importFromRSSL() { + FileNameExtensionFilter fileTypeFilter = new FileNameExtensionFilter("Reaction System Specification Language (rs)", "rs"); + JFileChooser fc = new JFileChooser(); + fc.addChoosableFileFilter(fileTypeFilter); + fc.setAcceptAllFileFilterUsed(true); + fc.setCurrentDirectory(new File(System.getProperty("user.dir"))); + + + if(fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { + File rsslFile = fc.getSelectedFile(); + + try { + File xmlFile = reactics.convertToXMLFile(rsslFile); + loadFromXMLFile(xmlFile); + } catch (IOException e) { + System.err.println("IOException: " + e.getMessage()); + e.printStackTrace(); + } catch (ReacticsRuntimeException e) { + System.err.println("ReacticsRuntimeException: " + e.getMessage()); + e.printStackTrace(); + } + + setTitle("ReactICS GUI : " + rsslFile.getName()); + contextAutomatonEditor.recalculateCoordinates(); + } + + modulePane.setSelectedIndex(0); + clearModificationStatus(); + } + + + private void exportToRSSLFile() { + File outFile = fileSelector.selectOutputFile(this, new File(System.getProperty("user.dir")), + new FileNameExtensionFilter("Reaction System Specification Language (RSSL)", "rs")); + + if(outFile == null) + return; + + System.out.println("[ReactICS] Export to RSSL file: " + outFile.getName()); + + try { + PrintWriter rsslOut = new PrintWriter(new FileWriter(outFile)); + printRSStructure(rsslOut); + rsslOut.close(); + } + catch(IOException ioe) { + System.err.println("[ReactICS] Error: " + ioe.getMessage()); + JOptionPane.showMessageDialog(this, ioe.getMessage(), + "Error", JOptionPane.ERROR_MESSAGE); + } + } + + + private void exportToISPLFile() { + updateReactionSystem(); + + if (!reactionSystem.isplTranslationAllowed()) { + JOptionPane.showMessageDialog(this, + "Context automaton contains guards for some transitions.\n" + + "Current version of ReactICS does not support translation to ISPL format in this case.\n", + "Translation is not possible", JOptionPane.INFORMATION_MESSAGE); + return; + } + + File outFile = fileSelector.selectOutputFile(this, new File(System.getProperty("user.dir")), + new FileNameExtensionFilter("Interpreted System Programming Language (ISPL)", "ispl")); + + if (outFile == null) + return; + + System.out.println("[ReactICS] Export to ISPL file: " + outFile.getAbsolutePath()); + + try { + if (!outFile.exists() && !outFile.createNewFile()) { + System.err.println("[ReactICS] Error: The file "+ outFile.getName() + " could not be created."); + JOptionPane.showMessageDialog(this, + "The file " + outFile.getName() + " could not be created.", + "Error", JOptionPane.ERROR_MESSAGE); + return; + } + + if (!outFile.canWrite()){ + System.err.println("[ReactICS] Error: Cannot write to the file " + outFile.getName()); + JOptionPane.showMessageDialog(this, + "Cannot write to the file " + outFile.getName(), + "Error", JOptionPane.ERROR_MESSAGE); + return; + } + + reactics.exportToISPLFile(outFile); + } + catch(IOException ioe) { + System.err.println("[ReactICS] Error: " + ioe.getMessage()); + JOptionPane.showMessageDialog(this, ioe.getMessage(), + "Error", JOptionPane.ERROR_MESSAGE); + } + catch(ReacticsRuntimeException rre) { + System.err.println("[ReactICS] Runtime error: " + rre.getMessage()); + JOptionPane.showMessageDialog(this, + rre.getMessage(), + "ReactICS Runtime Error", JOptionPane.ERROR_MESSAGE); + } + } + + private void printRSStructure(PrintWriter outStream) { + outStream.println("options { use-context-automaton; make-progressive; };\n"); + outStream.println(reactionSystemEditor.toRSSLString()); + outStream.println(contextAutomatonEditor.toRSSLString()); + } + + private void showAboutWindow() { + AboutWindow awin = AboutWindow.getInstance(); + awin.show(this); + } + + private void showHelpWindow() { + HelpWindow hwin = HelpWindow.getInstance(); + hwin.show(this); + } + + public static void main(String args[]) { + ReacticsGUI wnd = new ReacticsGUI(); + wnd.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + wnd.setLocationRelativeTo(null); + wnd.setVisible(true); + } + +} + + +//------------------------------------------------------------------------------------------------- +// Class responsible for selecting a file to load / save the reaction system structure +//------------------------------------------------------------------------------------------------- + +class FileSelector { + private enum Status {SELECT, APPROVE, CANCEL} + + private static FileSelector instance = null; + + public static synchronized FileSelector getInstance() { + if (instance == null) + instance = new FileSelector(); + + return instance; + } + + private FileSelector() { } + + public File selecInputFile(Component parent, File rootDir, FileNameExtensionFilter filter) { + Status opStatus = Status.SELECT; + JFileChooser fc = new JFileChooser(); + fc.addChoosableFileFilter(filter); + fc.setAcceptAllFileFilterUsed(true); + fc.setCurrentDirectory(rootDir); + File outFile = null; + + do { + if (fc.showOpenDialog(parent) != JFileChooser.APPROVE_OPTION) { + opStatus = Status.CANCEL; + break; + } + + outFile = fc.getSelectedFile(); + opStatus = Status.APPROVE; + + if(outFile.exists()) { + if (JOptionPane.showConfirmDialog(parent, + "The file " + outFile.getName() + " already exists.\n" + + "Do you wish to overwrite it?", "Warning", + JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.NO_OPTION) { + opStatus = Status.SELECT; + } + } + } while(opStatus == Status.SELECT); + + if(opStatus != Status.APPROVE) + outFile = null; + + return outFile; + } + + public File selectOutputFile(Component parent, File rootDir, FileNameExtensionFilter filter) { + Status opStatus = Status.SELECT; + JFileChooser fc = new JFileChooser(); + fc.addChoosableFileFilter(filter); + fc.setAcceptAllFileFilterUsed(true); + fc.setCurrentDirectory(rootDir); + File outFile = null; + + do { + if (fc.showSaveDialog(parent) != JFileChooser.APPROVE_OPTION) { + opStatus = Status.CANCEL; + break; + } + + outFile = fc.getSelectedFile(); + opStatus = Status.APPROVE; + + if(outFile.exists()) { + if (JOptionPane.showConfirmDialog(parent, + "The file " + outFile.getName() + " already exists.\n" + + "Do you wish to overwrite it?", "Warning", + JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.NO_OPTION) { + opStatus = Status.SELECT; + } + } + } while(opStatus == Status.SELECT); + + if(opStatus != Status.APPROVE) + outFile = null; + + return outFile; + } +} + + +/** + * General exception class for any error related to input XML file + */ +class FileStructureError extends Exception { + public FileStructureError(String message) { + super(message); + } +} \ No newline at end of file diff --git a/reactics-gui/src/pl/umk/mat/martinp/reactics/ReacticsRuntime.java b/reactics-gui/src/pl/umk/mat/martinp/reactics/ReacticsRuntime.java new file mode 100644 index 0000000..d8c73ea --- /dev/null +++ b/reactics-gui/src/pl/umk/mat/martinp/reactics/ReacticsRuntime.java @@ -0,0 +1,198 @@ +package pl.umk.mat.martinp.reactics; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; + + +public class ReacticsRuntime { + private static ReacticsRuntime instance = null; + + private String rctPath = "./"; + private String rctRuntime = "reactics"; + private Path rsslFile; + + public static synchronized ReacticsRuntime getInstance() throws ReacticsRuntimeException { + if (instance == null) + instance = new ReacticsRuntime(); + + return instance; + } + + private ReacticsRuntime() throws ReacticsRuntimeException { + try { + rsslFile = Files.createTempFile("reaction_system_", ".rs"); + rsslFile.toFile().deleteOnExit(); + System.out.println("[ReactICS] Created temporary file: " + rsslFile.toAbsolutePath()); + } + catch (IOException e) { + System.err.println("[ReactICS] Problem creating temporary file: + e.getMessage()"); + throw new ReacticsRuntimeException("Problem creating temporary file:\n" + e.getMessage()); + } + } + + public Path getRSSLFile() { + return rsslFile; + } + + public void loadConfig(String configFileName) throws ConfigReadingError, InvalidRuntimeException { + System.out.println("[ReactICS] Loading configuration from: " + configFileName); + + try { + BufferedReader inputStream = new BufferedReader(new FileReader(configFileName)); + String line; + + while ((line = inputStream.readLine()) != null) { + if (line.startsWith("runtime")) { + line = line.replaceAll("\\s", ""); + rctRuntime = line.substring(line.indexOf(":") + 1); + } + else if (line.startsWith("path")) { + line = line.replaceAll("\\s", ""); + rctPath = line.substring(line.indexOf(":") + 1); + } + } + } + catch (IOException e) { + throw new ConfigReadingError(e.getMessage()); + } + + File rctRntFile = new File(rctPath + rctRuntime); + + if (! (rctRntFile.exists() && rctRntFile.canExecute()) ) { + throw new InvalidRuntimeException(rctPath + rctRuntime + " cannot be executed"); + } + + System.out.println("[ReactICS] Using runtime: " + rctPath + rctRuntime); + } + + public EvalResult evaluateFormula(String fLabel) throws IOException, ReacticsRuntimeException { + // Drop "-B" option if verification time should not be measured + Process proc = Runtime.getRuntime().exec(rctPath + rctRuntime + " -B -c " + fLabel + " " + rsslFile.toAbsolutePath(), + null, new File(rctPath)); + + BufferedReader formResultStream = new BufferedReader(new InputStreamReader(proc.getInputStream())); + String line; + Formula.FormulaStatus result = Formula.FormulaStatus.None; + String mcTime = ""; + String totalTime = ""; + String memory = ""; + + while ((line = formResultStream.readLine()) != null) { + if (line.contains("holds")) { + result = Formula.FormulaStatus.True; + } + else if (line.contains("not hold")) { + result = Formula.FormulaStatus.False; + } + else if (line.startsWith("STAT")) { + String[] tokens = line.split(";"); + mcTime = tokens[2]; + totalTime = tokens[5]; + memory = tokens[4]; + } + + //TODO: Handle incorrect formulas + } + + StringBuilder errMsg = new StringBuilder(); + BufferedReader procErrStr = new BufferedReader(new InputStreamReader(proc.getErrorStream())); + while ((line = procErrStr.readLine()) != null) { + errMsg.append(line).append("\n"); + } + + if (!errMsg.isEmpty()) + throw new ReacticsRuntimeException(errMsg.toString()); + + return new EvalResult(result, mcTime, totalTime, memory); + } + + /** Complete Transition System. Context automaton state included. */ + public String getTransitionSystemStructure() throws IOException, ReacticsRuntimeException { + Process proc = Runtime.getRuntime().exec(rctPath + rctRuntime + " -t -X " + rsslFile.toAbsolutePath(), + null, new File(rctPath)); + + String line; + StringBuilder tsString = new StringBuilder(); + BufferedReader procOutStr = new BufferedReader(new InputStreamReader(proc.getInputStream())); + while ((line = procOutStr.readLine()) != null) { + tsString.append(line).append("\n"); + } + + StringBuilder errMsg = new StringBuilder(); + BufferedReader procErrStr = new BufferedReader(new InputStreamReader(proc.getErrorStream())); + while ((line = procErrStr.readLine()) != null) { + errMsg.append(line).append("\n"); + } + + if (!errMsg.isEmpty()) + throw new ReacticsRuntimeException(errMsg.toString()); + + return tsString.toString(); + } + + public void exportToISPLFile(File outFile) throws IOException, ReacticsRuntimeException { + Process proc = Runtime.getRuntime().exec(rctPath + rctRuntime + " -e " + rsslFile.toAbsolutePath() + " > " + outFile.getAbsolutePath(), + null, new File(rctPath)); + + String line; + BufferedReader procOutStr = new BufferedReader(new InputStreamReader(proc.getInputStream())); + PrintWriter isplOutStr = new PrintWriter(new FileWriter(outFile)); + while ((line = procOutStr.readLine()) != null) { + isplOutStr.println(line); + } + isplOutStr.close(); + + StringBuilder errMsg = new StringBuilder(); + BufferedReader procErrStr = new BufferedReader(new InputStreamReader(proc.getErrorStream())); + while ((line = procErrStr.readLine()) != null) { + errMsg.append(line).append("\n"); + } + + if (!errMsg.isEmpty()) + throw new ReacticsRuntimeException(errMsg.toString()); + } + + + public File convertToXMLFile(File rsslInputFile) throws IOException, ReacticsRuntimeException { + File xmlFile = Files.createTempFile("_" + rsslInputFile.getName(), ".xml").toFile(); + xmlFile.deleteOnExit(); + + ProcessBuilder procBuilder = new ProcessBuilder(rctPath + rctRuntime, "-y", rsslInputFile.getAbsolutePath()); + procBuilder.redirectOutput(new File(xmlFile.getAbsolutePath())); + Process proc = procBuilder.start(); + + String line; + StringBuilder errMsg = new StringBuilder(); + BufferedReader procErrStr = new BufferedReader(new InputStreamReader(proc.getErrorStream())); + while ((line = procErrStr.readLine()) != null) { + errMsg.append(line).append("\n"); + } + + if (!errMsg.isEmpty()) + throw new ReacticsRuntimeException(errMsg.toString()); + + return xmlFile; + } + + + record EvalResult(Formula.FormulaStatus result, String mcTime, String totalTime, String memory) {} +} + +class ConfigReadingError extends Exception { + public ConfigReadingError(String message) { + super(message); + } +} + +class InvalidRuntimeException extends Exception { + public InvalidRuntimeException(String description) { + super(description); + } +} + +class ReacticsRuntimeException extends Exception { + public ReacticsRuntimeException(String description) { + super(description); + } +} \ No newline at end of file diff --git a/reactics-gui/src/pl/umk/mat/martinp/reactics/ReactionSystemEditor.java b/reactics-gui/src/pl/umk/mat/martinp/reactics/ReactionSystemEditor.java new file mode 100644 index 0000000..51a86c9 --- /dev/null +++ b/reactics-gui/src/pl/umk/mat/martinp/reactics/ReactionSystemEditor.java @@ -0,0 +1,409 @@ +/** A component for editing the reaction system details */ + +package pl.umk.mat.martinp.reactics; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.PrintWriter; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.Vector; + + +//------------------------------------------------------------------------------------------------- +/** A graphical component allowing edition and display of a distributed reaction system. + * A distributed reaction system contains a number of processes. + * Each process contains a list of reactions. + */ +public class ReactionSystemEditor extends JPanel implements RSObserver { + ReactionSystem rs; + private Vector procEditors = new Vector(); + private JPanel processPanel = new JPanel(); + + + public ReactionSystemEditor() { + rs = ReactionSystem.getInstance(); + + processPanel.setLayout(new BoxLayout(processPanel, BoxLayout.Y_AXIS)); + + final Dimension buttonSize = new Dimension(150, 25); + + JButton addButton = new JButton("New process"); + addButton.setMaximumSize(buttonSize); + addButton.setPreferredSize(buttonSize); + addButton.setMaximumSize(buttonSize); + addButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + createProcess(); + } + }); + + JButton rmButton = new JButton("Remove"); + rmButton.setMaximumSize(buttonSize); + rmButton.setPreferredSize(buttonSize); + rmButton.setMaximumSize(buttonSize); + rmButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + removeProcesses(); + } + }); + + JButton copyButton = new JButton("Copy"); + copyButton.setMaximumSize(buttonSize); + copyButton.setPreferredSize(buttonSize); + copyButton.setMaximumSize(buttonSize); + copyButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + copyProcesses(); + } + }); + + JButton renameButton = new JButton("Rename"); + renameButton.setMaximumSize(buttonSize); + renameButton.setPreferredSize(buttonSize); + renameButton.setMaximumSize(buttonSize); + renameButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + renameProcess(); + } + }); + + JButton upButton = new JButton("Move up"); + upButton.setMaximumSize(buttonSize); + upButton.setPreferredSize(buttonSize); + upButton.setMaximumSize(buttonSize); + upButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + moveSelectedUp(); + } + }); + + JButton downButton = new JButton("Move down"); + downButton.setMaximumSize(buttonSize); + downButton.setPreferredSize(buttonSize); + downButton.setMaximumSize(buttonSize); + downButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + moveSelectedDown(); + } + }); + + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.Y_AXIS)); + + buttonPanel.add(addButton); + buttonPanel.add(rmButton); + buttonPanel.add(copyButton); + buttonPanel.add(renameButton); + buttonPanel.add(upButton); + buttonPanel.add(downButton); + + this.setLayout(new BorderLayout()); + this.add(new JScrollPane(processPanel), BorderLayout.CENTER); + this.add(buttonPanel, BorderLayout.WEST); + + createProcess(); + } + + public boolean isModified() { + boolean procModified = false; + + for (ProcessEditor pe : procEditors) + procModified |= pe.isModified(); + + return rs.modified | procModified; + } + + public void clearModificationStatus() { + for (ProcessEditor pe : procEditors) + pe.clearModificationStatus(); + + rs.modified = false; + } + + public Set getReactantsSet() { + HashSet rset = new HashSet(); + + for (ProcessEditor pe : procEditors) + rset.addAll(pe.getReactantsSet()); + + return rset; + } + + public void exportToXML(PrintWriter output) { + output.println(" "); + + for (ProcessEditor process : procEditors) + process.exportToXML(output); + + output.println(" "); + } + + public void loadFromXML(Document input) throws ReactionSystemStructureError { + NodeList rsSections = input.getElementsByTagName("reaction-system"); + + if (rsSections.getLength() == 0 || rsSections.getLength() > 1) { + throw new ReactionSystemStructureError("An XML file should contain a single reaction system description."); + } + + NodeList processList = rsSections.item(0).getChildNodes(); + + for (int idx=0; idx toRemove = new Vector(); + for (ProcessEditor edt : procEditors) { + if (edt.isSelected()) { + toRemove.add(edt); + } + } + + if (toRemove.size() == procEditors.size()) { + JOptionPane.showMessageDialog(this, "There should be at least a single process in the system.", + "Warning", JOptionPane.WARNING_MESSAGE); + return; + } + + for (ProcessEditor edt : toRemove) { + if (rs.removeProcess(edt.getProcess())) { + processPanel.remove(edt); + procEditors.remove(edt); + } + else { + JOptionPane.showMessageDialog(this, "Process " + edt.getLabel() + + " appears in a formula or a context automaton guard.\n" + + "Update them before removing the process.", + "Warning", JOptionPane.WARNING_MESSAGE); + } + } + + rs.modified = true; + revalidate(); + } + + private void copyProcesses() { + Vector toBeCopied = new Vector(); + + for (ProcessEditor edt : procEditors) { + if (edt.isSelected()) { + toBeCopied.add(edt); + } + } + + for (ProcessEditor edt : toBeCopied) { + ProcessEditor newEdt = new ProcessEditor(rs.copyProcess(edt.getProcess())); + processPanel.add(newEdt); + procEditors.add(newEdt); + } + + rs.modified = true; + revalidate(); + } + + private void renameProcess() { + ProcessEditor toRename = null; + boolean fail = false; + + for (ProcessEditor edt : procEditors) { + if (edt.isSelected()) { + if (toRename == null) { + toRename = edt; + } + else { + fail = true; + break; + } + } + } + + if (fail || toRename == null) { + JOptionPane.showMessageDialog(this, "Select a single process to rename.", "Select process", JOptionPane.WARNING_MESSAGE); + return; + } + + String idRegex = "[a-zA-Z][a-zA-Z_0-9:-]*"; + boolean idOk = false; + + do { + String newLabel = JOptionPane.showInputDialog(this, "Process name", toRename.getProcess().label); + if (newLabel == null) + return; + + newLabel = newLabel.trim(); + + if (!newLabel.matches(idRegex)) { + JOptionPane.showMessageDialog(this, + "Process name should start with a letter and may contain only:
" + + "letters, numbers, colons (:), underscores (_) and hyphens (-)", + "Error", JOptionPane.ERROR_MESSAGE); + } + else { + idOk = true; + rs.renameProcess(toRename.getProcess(), newLabel); + toRename.rename(newLabel); + rs.notifyObservers(); + } + } + while (!idOk); + + } + + private void moveSelectedUp() { + Vector toBeMovedIds = new Vector(); + for (int i = 0; i< procEditors.size(); ++i) { + if (procEditors.elementAt(i).isSelected()) + toBeMovedIds.add(i); + } + + for (int pos : toBeMovedIds) { + if (pos == 0 || procEditors.elementAt(pos-1).isSelected()) + continue; + + Collections.swap(procEditors, pos, pos-1); + } + + processPanel.removeAll(); + + for (ProcessEditor edt : procEditors) { + processPanel.add(edt); + } + + rs.modified = true; + revalidate(); + } + + private void moveSelectedDown() { + Vector toBeMovedIds = new Vector(); + + for (int i = procEditors.size()-1; i>=0; --i) { + if (procEditors.elementAt(i).isSelected()) + toBeMovedIds.add(i); + } + + for (int pos : toBeMovedIds) { + if (pos == procEditors.size()-1 || procEditors.elementAt(pos+1).isSelected()) + continue; + + Collections.swap(procEditors, pos, pos+1); + } + + processPanel.removeAll(); + + for (ProcessEditor edt : procEditors) { + processPanel.add(edt); + } + + rs.modified = true; + revalidate(); + } + + public void onRSUpdate() { + repaint(); + } + +} + + +class ReactionSystemStructureError extends FileStructureError { + public ReactionSystemStructureError(String message) { + super(message); + } +} \ No newline at end of file diff --git a/reactics-gui/src/pl/umk/mat/martinp/reactics/TransitionEditor.java b/reactics-gui/src/pl/umk/mat/martinp/reactics/TransitionEditor.java new file mode 100644 index 0000000..b7083af --- /dev/null +++ b/reactics-gui/src/pl/umk/mat/martinp/reactics/TransitionEditor.java @@ -0,0 +1,208 @@ +package pl.umk.mat.martinp.reactics; + +import javax.swing.*; +import javax.swing.table.AbstractTableModel; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.*; + + +class TransitionEditor extends JDialog { + private JTable transitionsList; + private TransitionTableModel transitionsModel; + private Vector transitions; + + public TransitionEditor() { + super(); + + // Create reactions list component + transitionsModel = new TransitionTableModel(); + transitionsList = new JTable(transitionsModel); + + Font cellFont = transitionsList.getFont(); + Font headerFont = transitionsList.getTableHeader().getFont(); + + transitionsList.setFont(new Font("Monospaced", 0, cellFont.getSize() + 2)); + transitionsList.getTableHeader().setFont(new Font(headerFont.getFamily(), Font.BOLD, headerFont.getSize() + 2)); + transitionsList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + + getContentPane().setLayout(new BorderLayout()); + + JButton addButton = new JButton("Add transition"); + addButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { addTransition(); } + }); + + JButton edtButton = new JButton("Edit transition"); + edtButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { editTransition(); } + }); + + JButton rmButton = new JButton("Remove transition"); + rmButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { removeTransitions(); } + }); + + JButton closeButton = new JButton("Close"); + closeButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { setVisible(false); } + }); + + JPanel buttonPanel = new JPanel(); + buttonPanel.add(addButton); + buttonPanel.add(edtButton); + buttonPanel.add(rmButton); + buttonPanel.add(closeButton); + + getContentPane().add(buttonPanel, BorderLayout.NORTH); + getContentPane().add(new JScrollPane(transitionsList), BorderLayout.CENTER); + + setPreferredSize(new Dimension(-1, 200)); + setSize(new Dimension(600, 200)); + setTitle("Transitions"); + } + + public void showTransitionEditDialog(Component parent, Collection trans, String from, String to) { + setTitle("Transitions for (" + from + " -> " + to + ")"); + transitions = (Vector) trans; + transitionsModel.fireTableDataChanged(); + setLocationRelativeTo(parent); + setVisible(true); + } + + private void addTransition() { + Transition tr = new Transition("", ""); + + if (!showTransitionEditDialog(tr)) + return; + + transitions.add(tr); + transitionsModel.fireTableDataChanged(); + } + + private void editTransition() { + int[] selected = transitionsList.getSelectedRows(); + + if (selected.length != 1) { + JOptionPane.showMessageDialog(this, "Select a single transition to edit.", "Select transition", JOptionPane.WARNING_MESSAGE); + return; + } + + Transition tr = transitions.get(selected[0]); + transitionsList.clearSelection(); + if (!showTransitionEditDialog(tr)) { + return; + } + + transitions.setElementAt(tr, selected[0]); + transitionsModel.fireTableDataChanged(); + } + + private void removeTransitions() { + int[] selected = transitionsList.getSelectedRows(); + + for (int trIdx : selected) { + transitions.remove(trIdx); + transitionsList.clearSelection(); + } + + transitionsModel.fireTableDataChanged(); + } + + private boolean showTransitionEditDialog(Transition tr) { + final int Select = 0; + final int Approve = 1; + + JTextField contextInput = new JTextField(30); + JTextField guardInput = new JTextField(30); + + String contextStr = tr.context; + String guardStr = tr.guard; + + contextInput.setToolTipText("Additional entities for processes"); + guardInput.setToolTipText("Initial conditions for the transition in rsCTL format."); + + JPanel myPanel = new JPanel(new GridBagLayout()); + GridBagConstraints cs = new GridBagConstraints(); + + cs.insets = new Insets(5, 5, 5, 5); + cs.anchor = GridBagConstraints.WEST; + cs.gridx = 0; + cs.gridy = 0; + myPanel.add(new JLabel("Context"), cs); + + cs.gridx = 1; + cs.gridy = 0; + myPanel.add(contextInput, cs); + + cs.gridx = 0; + cs.gridy = 1; + myPanel.add(new JLabel("Guards"), cs); + + cs.gridx = 1; + cs.gridy = 1; + myPanel.add(guardInput, cs); + + int opStatus = Select; + + do { + contextInput.setText(contextStr); + guardInput.setText(guardStr); + + int result = JOptionPane.showConfirmDialog(null, myPanel, + "Reaction details", JOptionPane.OK_CANCEL_OPTION); + + if (result != JOptionPane.OK_OPTION) { + return false; + } + + contextStr = contextInput.getText(); + guardStr = guardInput.getText(); + + //TODO: Validate the data entered to the text fields + tr.context = contextStr; + tr.guard = guardStr; + + opStatus = Approve; + } + while(opStatus == Select); + + return true; + } + + + class TransitionTableModel extends AbstractTableModel { + + private final String[] columnNames = {"Context", "Guard"}; + + public String getColumnName(int colIdx) { + return columnNames[colIdx]; + } + + public int getRowCount() { + return transitions.size(); + } + + public int getColumnCount() { + return columnNames.length; + } + + + public Object getValueAt(int row, int column) { + Transition tr = transitions.get(row); + + return switch (column) { + case 0 -> tr.context; + case 1 -> tr.guard; + default -> ""; + }; + } + + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + } + +} + diff --git a/reactics-gui/src/pl/umk/mat/martinp/reactics/TransitionSystemViewer.java b/reactics-gui/src/pl/umk/mat/martinp/reactics/TransitionSystemViewer.java new file mode 100644 index 0000000..82c8949 --- /dev/null +++ b/reactics-gui/src/pl/umk/mat/martinp/reactics/TransitionSystemViewer.java @@ -0,0 +1,367 @@ +package pl.umk.mat.martinp.reactics; + +import edu.uci.ics.jung.algorithms.layout.*; +import edu.uci.ics.jung.algorithms.layout.util.Relaxer; +import edu.uci.ics.jung.graph.DirectedSparseGraph; +import edu.uci.ics.jung.visualization.VisualizationViewer; +import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; +import edu.uci.ics.jung.visualization.control.ModalGraphMouse; +import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; +import edu.uci.ics.jung.visualization.renderers.Renderer; +import org.apache.commons.collections15.Transformer; + +import javax.swing.JPanel; +import javax.swing.JToolTip; +import javax.swing.Icon; +import javax.swing.border.EtchedBorder; +import java.awt.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class TransitionSystemViewer extends JPanel implements RSObserver { + + //--------------------------------------------------------------- + // Visual configuration settings + //--------------------------------------------------------------- + + private static final Color pickedNodeColor = Color.yellow; + private static final Color edgeColor = Color.black; + private static final Font nodeLabelFont = new Font("Helvetica", Font.BOLD, 20); + private static final Font toolTipFont = new Font("Helvetica", Font.PLAIN, 14); + private static final Stroke basicEdgeArrow = new BasicStroke(3.0f); + + private static Dimension initialEditorSize; + + private VisualizationViewer graphViewer; + private Layout graphLayout; + DefaultModalGraphMouse graphMouse; + + //--------------------------------------------------------------- + // Transition system graph + //--------------------------------------------------------------- + + private DirectedSparseGraph tsGraph; + private boolean isCompressed; + + // Is transition system already computed? + // Used to avoid unnecessary computation of the same graph. + private boolean computed = false; + + // compressed == true => context automaton state is not taken into account. + public TransitionSystemViewer(Dimension size, boolean compressed) { + this.setLayout(new BorderLayout()); + tsGraph = new DirectedSparseGraph(); + this.isCompressed = compressed; + initialEditorSize = size; + + createGraphViever(); + lockNodesPositions(false); + } + + // Is transition system graph already computed? + boolean isComputed() { return computed; } + + void clearModificationStatus() { computed = false; } + + private void createGraphViever() { + graphLayout = new FRLayout(tsGraph); + + graphViewer = new VisualizationViewer(graphLayout, initialEditorSize) { + public JToolTip createToolTip() { + JToolTip tooltip = super.createToolTip(); + tooltip.setFont(toolTipFont); + return tooltip; + } + }; + + // Nodes labels + graphViewer.getRenderContext().setVertexLabelTransformer( + new ToStringLabeller()); + graphViewer.getRenderer().getVertexLabelRenderer() + .setPosition(Renderer.VertexLabel.Position.CNTR); + graphViewer.getRenderContext().setVertexFontTransformer( + new Transformer() { + public Font transform(TSState st) { + return nodeLabelFont; + } + }); + + // Nodes colour, background, etc. + graphViewer.getRenderContext().setVertexIconTransformer(new Transformer() { + public Icon transform(final TSState v) { + Color nodeColor = null; + + if(graphViewer.getPickedVertexState().isPicked(v)) + nodeColor = pickedNodeColor; + else + nodeColor = v.getFillColor(); + + return (v.isInitial() ? new SquareIcon(nodeColor, v.getWidth()) : new CircleIcon(nodeColor, v.getWidth())); + + }}); + + graphViewer.getRenderContext().setEdgeShapeTransformer(new ConditionalEdgeShape<>(1.5f)); + + // Edges + graphViewer.getRenderContext().setEdgeArrowStrokeTransformer(new Transformer() { + public Stroke transform(TSTransition tr) { + return basicEdgeArrow; + } + }); + + // For interactive graph editing + graphMouse = new DefaultModalGraphMouse(); + graphViewer.setGraphMouse(graphMouse); + graphMouse.setMode(ModalGraphMouse.Mode.PICKING); + + graphViewer.setVertexToolTipTransformer(new Transformer() { + public String transform(TSState st) { + return st.getTooltipText(); + } + }); + + graphViewer.setBackground(Color.white); + graphViewer.setBorder(new EtchedBorder(EtchedBorder.LOWERED)); + + add(graphViewer, BorderLayout.CENTER); + } + + + public void updateTransitionSystemStructure(String tsString) { + // Clear graph + clear(); + + // Load graph structure + int nextId = 1; + + HashMap nodes = new HashMap<>(); + TSState src = null; + + for (String line : tsString.split("\\R")) { + char type = line.charAt(0); + line = line.substring(2).strip(); + int end = line.lastIndexOf('}'); + String automatonState = line.substring(end + 1, line.length() - 1).strip(); + String stateStr = line.substring(line.indexOf('{'), end + 1); + TSState state = nodes.get( isCompressed ? stateStr : line); + + if (state == null) { + state = new TSState(nextId++, stateStr, isCompressed ? null : automatonState); + nodes.put(isCompressed ? stateStr : line, state); + tsGraph.addVertex(state); + } + + if (type == 'G') { + src = state; + } + else if (type == 's') { + tsGraph.addEdge(new TSTransition(), src, state); + } + } + + // Find the initial state + for (TSState state : tsGraph.getVertices()) { + if (tsGraph.getInEdges(state).size() == 0) { + state.setAsInitial(); + break; + } + } + + relax(); + graphViewer.repaint(); + computed = true; + } + + + public void lockNodesPositions(boolean lock) { + if (lock) + graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING); + else + graphMouse.setMode(ModalGraphMouse.Mode.PICKING); + } + + + public void clear() { + for (TSState st : new java.util.ArrayList(tsGraph.getVertices())) { + tsGraph.removeVertex(st); + } + + repaint(); + } + + + /** For better vertex placement */ + private void relax() { + graphLayout.initialize(); + graphLayout.setSize(graphViewer.getSize()); + Relaxer relaxer = graphViewer.getModel().getRelaxer(); + + if (relaxer != null) { + relaxer.stop(); + relaxer.prerelax(); + relaxer.relax(); + } + } + + + //--------------------------------------------------------------------------------------------- + // For drawing the initial state + //--------------------------------------------------------------------------------------------- + + static class SquareIcon implements Icon { + private final Color fillColor; + private final int width; + + public SquareIcon(Color fColor, int width) { + fillColor = fColor; + this.width = width; + } + + public void paintIcon(Component c, Graphics g, int x, int y) { + g.setColor(fillColor); + g.fillRect(x, y, width, width); + g.setColor(Color.black); + g.drawRect(x, y, width, width); + } + + public int getIconWidth() { + return width; + } + + public int getIconHeight() { + return width; + } + } + + + //--------------------------------------------------------------------------------------------- + // For drawing all non-initial states + //--------------------------------------------------------------------------------------------- + + static class CircleIcon implements Icon { + private final Color fillColor; + private final int radius; + + public CircleIcon(Color fColor, int radius) { + fillColor = fColor; + this.radius = radius; + } + + public void paintIcon(Component c, Graphics g, int x, int y) { + g.setColor(fillColor); + g.fillOval(x, y, radius, radius); + g.setColor(Color.black); + g.drawOval(x, y, radius, radius); + } + + public int getIconWidth() { + return radius; + } + + public int getIconHeight() { + return radius; + } + } + + public void onRSUpdate() { + clear(); + } + +} + + +//------------------------------------------------------------------------------------------------- +// Single node of the Transition system graph +//------------------------------------------------------------------------------------------------- + +class TSState { + + public final static Color fillColor = Color.lightGray; + public final static Color initialColor = new Color(102, 178, 255); + + private static final int nodeWidth = 30; + private static final int nodeHeight = 30; + + private final int id; + private final String rsDetails; + private final String toolTipText; + private final String aState; + private boolean initial = false; + + public TSState(int id, String stateStr) { + this.id = id; + this.rsDetails = formatRSDetails(stateStr.substring(1, stateStr.length() - 1).strip()); + this.aState = null; + this.toolTipText = formatToolTipText(); + } + + public TSState(int id, String stateStr, String aState) { + this.id = id; + this.rsDetails = formatRSDetails(stateStr.substring(1, stateStr.length() - 1).strip()); + this.aState = aState; + this.toolTipText = formatToolTipText(); + } + + public boolean isInitial() { + return initial; + } + + public void setAsInitial() { + initial = true; + } + + public String toString() { + return String.valueOf(id); + } + + public int getWidth() { + return nodeWidth; + } + + public int getHeight() { + return nodeHeight; + } + + Color getFillColor() { + return initial ? initialColor : fillColor; + } + + public String getTooltipText() { + return toolTipText; + } + + private String formatRSDetails(String rsDetails) { + return rsDetails.replaceAll("\\} (proc\\d+=\\{)", "}
$1"); + } + + private String formatToolTipText() { + Pattern pattern = Pattern.compile("\\w+=\\{[^}]*\\}"); + Matcher matcher = pattern.matcher(rsDetails); + + ArrayList blocks = new ArrayList(); + + while (matcher.find()) { + blocks.add(matcher.group()); + } + + if (aState != null) + return "" + + "CA " + aState+ "

" + + String.join("
", blocks) + + ""; + else + return "" + + String.join("
", blocks) + + ""; + } +} + + +//------------------------------------------------------------------------------------------------- +// Single edge of the Transition system graph +//------------------------------------------------------------------------------------------------- + +class TSTransition { } -- 2.53.0 From f0019619b37ca541200258a67139fcd0a3f987e2 Mon Sep 17 00:00:00 2001 From: Artur Meski Date: Fri, 10 Apr 2026 18:16:49 +0100 Subject: [PATCH 11/12] Refactor + tests (#6) - Minor codebase clean-up - Slight reorganisation of examples + tests so that we don't break stuff by accident - CI tests --- .github/workflows/tests.yml | 99 ++++ TESTING.md | 65 +++ examples/bdd/generators/gen_asm.py | 103 ++++ examples/bdd/generators/gen_drs.py | 33 +- examples/bdd/generators/gen_tgc.py | 151 +++--- .../bdd/generators/old_syntax/gen_abstract.py | 62 +++ examples/bdd/generators/old_syntax/gen_bc.py | 88 +++ .../bdd/generators/old_syntax/gen_mutex.py | 71 +++ examples/bdd/{tgc.rs => tgc.drs} | 0 examples/bdd/tgc4.drs | 70 +++ reactics-bdd/ctx_aut.cc | 7 +- reactics-bdd/ctx_aut.hh | 3 - reactics-bdd/drs_benchmark.sh | 2 +- reactics-bdd/formrsctlk.cc | 49 -- reactics-bdd/formrsctlk.hh | 46 -- reactics-bdd/in/{hsr.rs => hsr.drs} | 0 reactics-bdd/in/{hsr_ca.rs => hsr_ca.drs} | 0 reactics-bdd/in/{hsr_drs.rs => hsr_drs.drs} | 0 .../in/old_syntax/{bc32.rs => bc32.drs} | 0 .../in/old_syntax/{bc8.rs => bc8.drs} | 0 .../in/old_syntax/{coffee.rs => coffee.drs} | 0 .../in/old_syntax/{expr.rs => expr.drs} | 0 .../{simple_I1.rs => simple_I1.drs} | 0 .../in/old_syntax/{test.rs => test.drs} | 0 reactics-bdd/in/old_syntax/{u1.rs => u1.drs} | 0 reactics-bdd/in/old_syntax/{u2.rs => u2.drs} | 0 reactics-bdd/in/scripts/bench_bc.sh | 6 +- reactics-bdd/in/scripts/bench_mutex.sh | 6 +- reactics-bdd/in/scripts/bench_pipe.sh | 6 +- reactics-bdd/in/scripts/benchmark.sh | 6 +- reactics-bdd/in/scripts/benchmark_abs1.sh | 6 +- reactics-bdd/in/scripts/benchmark_abs1_PT.sh | 6 +- reactics-bdd/in/scripts/benchmark_bc.sh | 6 +- reactics-bdd/in/scripts/benchmark_mutex.sh | 6 +- reactics-bdd/in/scripts/benchmark_mutex_PT.sh | 6 +- reactics-bdd/in/scripts/gen_abstract1.py | 47 -- reactics-bdd/in/scripts/gen_asm.py | 93 ---- reactics-bdd/in/scripts/gen_bc.py | 118 ----- reactics-bdd/in/scripts/gen_mutex.py | 76 --- reactics-bdd/in/scripts/gen_tgc_sc.py | 117 ---- reactics-bdd/in/{trivial.rs => trivial.drs} | 0 reactics-bdd/mc.cc | 12 +- reactics-bdd/reactics.cc | 17 +- reactics-bdd/rs.cc | 48 +- reactics-bdd/rs.hh | 1 + reactics-bdd/symrs.cc | 108 +--- reactics-bdd/symrs.hh | 13 - reactics-bdd/{test.rs => test.drs} | 0 reactics-smt/rs/context_automaton.py | 12 +- .../context_automaton_with_concentrations.py | 9 +- reactics-smt/rs/extended_context_automaton.py | 2 +- reactics-smt/rs/reaction_system.py | 7 +- .../rs/reaction_system_with_concentrations.py | 47 +- ...action_system_with_concentrations_param.py | 18 +- reactics-smt/rs_testing.py | 2 - reactics-smt/rssmt.py | 1 - reactics-smt/smt/__init__.py | 1 - reactics-smt/smt/smt_checker_rs.py | 2 +- reactics-smt/smt/smt_checker_rsc.py | 3 - reactics-smt/smt/smt_checker_rsc_param.py | 3 - run_tests.sh | 3 + tests/expected/tgc4_mc.txt | 8 + tests/expected/tgc4_print.txt | 58 ++ tests/expected/tgc4_states.txt | 80 +++ tests/expected/tgc_mc.txt | 8 + tests/expected/tgc_print.txt | 47 ++ tests/expected/tgc_reactions.txt | 23 + tests/expected/tgc_states.txt | 32 ++ tests/expected/trivial_print.txt | 17 + tests/expected/trivial_reactions.txt | 8 + tests/expected/trivial_states.txt | 3 + tests/run_all_tests.sh | 85 +++ tests/run_tests.sh | 69 +++ tests/test_smt.py | 501 ++++++++++++++++++ 74 files changed, 1672 insertions(+), 930 deletions(-) create mode 100644 .github/workflows/tests.yml create mode 100644 TESTING.md create mode 100644 examples/bdd/generators/gen_asm.py create mode 100644 examples/bdd/generators/old_syntax/gen_abstract.py create mode 100644 examples/bdd/generators/old_syntax/gen_bc.py create mode 100644 examples/bdd/generators/old_syntax/gen_mutex.py rename examples/bdd/{tgc.rs => tgc.drs} (100%) create mode 100644 examples/bdd/tgc4.drs rename reactics-bdd/in/{hsr.rs => hsr.drs} (100%) rename reactics-bdd/in/{hsr_ca.rs => hsr_ca.drs} (100%) rename reactics-bdd/in/{hsr_drs.rs => hsr_drs.drs} (100%) rename reactics-bdd/in/old_syntax/{bc32.rs => bc32.drs} (100%) rename reactics-bdd/in/old_syntax/{bc8.rs => bc8.drs} (100%) rename reactics-bdd/in/old_syntax/{coffee.rs => coffee.drs} (100%) rename reactics-bdd/in/old_syntax/{expr.rs => expr.drs} (100%) rename reactics-bdd/in/old_syntax/{simple_I1.rs => simple_I1.drs} (100%) rename reactics-bdd/in/old_syntax/{test.rs => test.drs} (100%) rename reactics-bdd/in/old_syntax/{u1.rs => u1.drs} (100%) rename reactics-bdd/in/old_syntax/{u2.rs => u2.drs} (100%) delete mode 100755 reactics-bdd/in/scripts/gen_abstract1.py delete mode 100755 reactics-bdd/in/scripts/gen_asm.py delete mode 100755 reactics-bdd/in/scripts/gen_bc.py delete mode 100755 reactics-bdd/in/scripts/gen_mutex.py delete mode 100755 reactics-bdd/in/scripts/gen_tgc_sc.py rename reactics-bdd/in/{trivial.rs => trivial.drs} (100%) rename reactics-bdd/{test.rs => test.drs} (100%) create mode 100755 run_tests.sh create mode 100644 tests/expected/tgc4_mc.txt create mode 100644 tests/expected/tgc4_print.txt create mode 100644 tests/expected/tgc4_states.txt create mode 100644 tests/expected/tgc_mc.txt create mode 100644 tests/expected/tgc_print.txt create mode 100644 tests/expected/tgc_reactions.txt create mode 100644 tests/expected/tgc_states.txt create mode 100644 tests/expected/trivial_print.txt create mode 100644 tests/expected/trivial_reactions.txt create mode 100644 tests/expected/trivial_states.txt create mode 100755 tests/run_all_tests.sh create mode 100755 tests/run_tests.sh create mode 100644 tests/test_smt.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..6f61a61 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,99 @@ +name: Tests + +on: + push: + branches: [master, refactor] + pull_request: + branches: [master] + +jobs: + bdd: + name: BDD tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y g++ make bison flex + + - name: Build CUDD + working-directory: reactics-bdd + run: ./build_cudd.sh + + - name: Build ReactICS BDD module + working-directory: reactics-bdd + run: make + + - name: Run BDD tests + run: bash tests/run_tests.sh + + smt: + name: SMT tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install Python dependencies + run: pip install z3-solver pytest + + - name: Run SMT tests + run: python3 -m pytest tests/test_smt.py -v + + generators: + name: Generator tests + runs-on: ubuntu-latest + needs: bdd + steps: + - uses: actions/checkout@v4 + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y g++ make bison flex + + - name: Build CUDD + working-directory: reactics-bdd + run: ./build_cudd.sh + + - name: Build ReactICS BDD module + working-directory: reactics-bdd + run: make + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Run generator tests + run: | + REACTICS="$PWD/reactics-bdd/reactics" + GENERATORS="$PWD/examples/bdd/generators" + failed=0 + check_gen() { + local name="$1" gen_cmd="$2" property="$3" expected="$4" + local tmpfile=$(mktemp /tmp/reactics_gen_XXXXXX.drs) + eval "$gen_cmd" > "$tmpfile" 2>&1 + local result=$("$REACTICS" -c "$property" "$tmpfile" 2>&1 | grep -oE "(holds|does not hold)" || true) + rm -f "$tmpfile" + if [ "$result" = "$expected" ]; then + echo " PASS: $name" + else + echo " FAIL: $name (expected '$expected', got '$result')" + failed=1 + fi + } + check_gen "tgc(3) f1" "python3 $GENERATORS/gen_tgc.py 3" f1 "holds" + check_gen "tgc(3) f3" "python3 $GENERATORS/gen_tgc.py 3" f3 "holds" + check_gen "tgc(4) f2" "python3 $GENERATORS/gen_tgc.py 4" f2 "holds" + check_gen "asm(3) f1" "python3 $GENERATORS/gen_asm.py 3" f1 "holds" + check_gen "asm(3) f2" "python3 $GENERATORS/gen_asm.py 3" f2 "holds" + check_gen "drs(2,2,2,a) f0" "python3 $GENERATORS/gen_drs.py 2 2 2 a" f0 "does not hold" + check_gen "drs(2,2,2,b) f0" "python3 $GENERATORS/gen_drs.py 2 2 2 b" f0 "holds" + exit $failed diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000..1c99d4b --- /dev/null +++ b/TESTING.md @@ -0,0 +1,65 @@ +# Testing ReactICS + +## Prerequisites + +- **BDD module**: compiled (`./reactics setup` or `make` in `reactics-bdd/`) +- **SMT module**: Python 3 with `z3-solver` and `pytest` installed + +``` +pip install z3-solver pytest +``` + +## Running all tests + +``` +./run_tests.sh +``` + +This runs the BDD, SMT, and generator test suites in sequence. + +## BDD tests + +``` +bash tests/run_tests.sh +``` + +10 regression tests that run the compiled `reactics-bdd/reactics` binary +against example `.drs` files and compare output to saved baselines in +`tests/expected/`. Covers: + +- Parsed system printing (`-P`) +- Reaction listing (`-r`) +- Reachable state enumeration (`-s`) +- RSCTLK model checking (`-c`) for properties f1--f4 + +Input files: `examples/bdd/tgc.drs`, `examples/bdd/tgc4.drs`, +`reactics-bdd/in/trivial.drs`. + +To update baselines after an intentional output change, delete the +relevant file in `tests/expected/` and re-run; the test will regenerate it. + +## SMT tests + +``` +python3 -m pytest tests/test_smt.py -v +``` + +23 pytest tests exercising the Python API directly. Organised by layer: + +- **Data model** -- ReactionSystem, concentrations, context automata, + formula construction (BagDescription, rsLTL) +- **SmtCheckerRS** -- basic reachability (no concentrations) +- **SmtCheckerRSC** -- reachability and rsLTL model checking with + concentrations (chain reaction, heat shock response) +- **SmtCheckerRSCParam** -- parametric verification +- **RSC-to-RS translation** -- verifying the translated system + +## Generator tests + +7 tests that run each generator (from `examples/bdd/generators/`), +feed the output into the BDD model checker, and verify the expected +verification result (holds / does not hold). + +Generators using old syntax (`examples/bdd/generators/old_syntax/`) +are not tested this way as they are not compatible with the current +parser. diff --git a/examples/bdd/generators/gen_asm.py b/examples/bdd/generators/gen_asm.py new file mode 100644 index 0000000..0f2e169 --- /dev/null +++ b/examples/bdd/generators/gen_asm.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +""" +Generator for the Assembly-line (ASM) benchmark. + +Produces a distributed reaction system modelling an assembly line +where N sequential processes pass activation tokens. Each process +cycles through entities a -> y,b -> c -> d, and the final process +signals 'done'. Uses a context automaton for scheduling. +""" + +import argparse + + +OPTIONS = "options { use-context-automaton; make-progressive; };\n" + +PROC_TEMPLATE = """ + proc{i} {{ + {{{{a}}, {{s}} -> {{y}}}}; + {{{{y}}, {{s}} -> {{y}}}}; + {{{{a}}, {{s}} -> {{b}}}}; + {{{{b}}, {{s}} -> {{c}}}}; + {{{{c}}, {{s}} -> {{d}}}}; + {{{{d,y}}, {{s}} -> {{dy}}}}; + }}; +""" + +FINAL_PROC = """ + procFinal { + {{done}, {s} -> {done}}; + }; +""" + +CA_TEMPLATE = """ +context-automaton {{ + states {{ init, act }}; + init-state {{ init }}; + transitions {{ +{transitions} + }}; +}}; +""" + +PROPERTY_TEMPLATE = '\nrsctlk-property {{ {name} : {formula} }};\n' + + +def generate(n): + out = OPTIONS + out += "\nreactions {\n" + for i in range(1, n + 1): + out += PROC_TEMPLATE.format(i=i) + out += FINAL_PROC + out += "};\n" + + indent = 8 * " " + transitions = "" + + # init -> act: first process gets activated + transitions += "{indent}{{ proc1={{a}} }}: init -> act;\n".format(indent=indent) + + # act -> act: process stays idle (dy not produced) + for i in range(1, n + 1): + transitions += "{indent}{{ proc{i}={{}} }}: act -> act : ~proc{i}.dy;\n".format( + indent=indent, i=i) + + # act -> act: next process gets activated when previous produces 'dy' + for i in range(2, n + 1): + transitions += "{indent}{{ proc{i}={{a}} }}: act -> act : proc{prev}.dy;\n".format( + indent=indent, i=i, prev=i - 1) + + # act -> act: final process signals done when all have produced 'dy' + all_dy = " AND ".join("proc{}.dy".format(i) for i in range(1, n + 1)) + transitions += "{indent}{{ procFinal={{done}} }}: act -> act : {guard};\n".format( + indent=indent, guard=all_dy) + + out += CA_TEMPLATE.format(transitions=transitions) + + # f1: the assembly line eventually completes + out += PROPERTY_TEMPLATE.format(name="f1", formula="EF( procFinal.done )") + + # f2: last process knows its predecessor has produced 'y' + f2 = "AG( proc{n}.d IMPLIES K[proc{n}]( proc{prev}.y ) )".format(n=n, prev=n - 1) + out += PROPERTY_TEMPLATE.format(name="f2", formula=f2) + + # x1: negation of f1 (sanity check -- should also hold) + out += PROPERTY_TEMPLATE.format(name="x1", formula="EF( ~procFinal.done )") + + return out + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("n", type=int, help="number of processes (must be > 1)") + args = parser.parse_args() + + if args.n < 2: + parser.error("number of processes must be > 1") + + print(generate(args.n)) + + +if __name__ == "__main__": + main() diff --git a/examples/bdd/generators/gen_drs.py b/examples/bdd/generators/gen_drs.py index c076f7a..c1f22a7 100755 --- a/examples/bdd/generators/gen_drs.py +++ b/examples/bdd/generators/gen_drs.py @@ -1,6 +1,15 @@ #!/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 @@ -272,18 +281,20 @@ class DRSGenerator: def main(): - if len(sys.argv) < 1 + 4: - print(f"Usage: {sys.argv[0]} ") - print("\twhere x,y,z >= 2 and y >= z") - print("\tAut: a, b") - sys.exit(1) + 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() - g = DRSGenerator( - x=int(sys.argv[1]), - y=int(sys.argv[2]), - z=int(sys.argv[3]), - aut=sys.argv[4], - ) + 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__": diff --git a/examples/bdd/generators/gen_tgc.py b/examples/bdd/generators/gen_tgc.py index d4bfcf0..32b8654 100755 --- a/examples/bdd/generators/gen_tgc.py +++ b/examples/bdd/generators/gen_tgc.py @@ -1,13 +1,20 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 +""" +Generator for the Traffic Guard Controller (TGC) benchmark. -from sys import argv,exit - -OPTIONS_STR = """ -options { use-context-automaton; make-progressive; }; +Produces a distributed reaction system where N processes compete +for access to a shared resource, governed by a context automaton +with green/red states. Generates RSCTLK properties for liveness, +reachability, and mutual exclusion (epistemic). """ -PROC_STR = """ - proc{:d} {{ +import argparse + + +OPTIONS = "options { use-context-automaton; make-progressive; };\n" + +PROC_TEMPLATE = """ + proc{i} {{ {{{{out}}, {{}} -> {{approach}}}}; {{{{approach}}, {{req}} -> {{req}}}}; {{{{allowed}}, {{}} -> {{in}}}}; @@ -16,102 +23,94 @@ PROC_STR = """ }}; """ -CA_STR = """ +CA_TEMPLATE = """ context-automaton {{ states {{ init, green, red }}; init-state {{ init }}; transitions {{ -{:s} +{transitions} }}; }}; """ -PROPERTY_STR = """ -rsctlk-property {{ {:s} : {:s} }}; -""" +PROPERTY_TEMPLATE = '\nrsctlk-property {{ {name} : {formula} }};\n' -################################################################# +def conj(fmt, indices, sep=" AND "): + return sep.join(fmt.format(i=i) for i in indices) -if len(argv) < 1: - print("Usage: {:s} ".format(argv[0])) - exit(100) -n = int(argv[1]) +def generate(n): + out = OPTIONS + out += "\nreactions {\n" + for i in range(n): + out += PROC_TEMPLATE.format(i=i) + out += "};\n" -assert n > 1, "number of proc must be > 1" + indent = 8 * " " + transitions = "" -out = "" + # init -> green: all processes start with 'out' + transitions += indent + "{ " + transitions += " ".join("proc{:d}={{out}}".format(i) for i in range(n)) + transitions += " }: init -> green;\n" -out += OPTIONS_STR -out += "reactions {\n" -for i in range(n): - out += PROC_STR.format(i) -out += "};\n" + # green -> red: a process requests + for i in range(n): + transitions += "{indent}{{ proc{i}={{allowed}} }}: green -> red : proc{i}.req;\n".format( + indent=indent, i=i) -transitions = "" + # green -> green: no requests pending + no_req = conj("~proc{i}.req", range(n)) + for i in range(n): + transitions += "{indent}{{ proc{i}={{}} }}: green -> green : {guard};\n".format( + indent=indent, i=i, guard=no_req) -init_trans = 8*" " + "{ " -for i in range(n): - init_trans += "proc{:d}={{out}} ".format(i) -init_trans += "}: init -> green;\n" -transitions += init_trans + # red -> green: a process leaves + for i in range(n): + transitions += "{indent}{{ proc{i}={{}} }}: red -> green : proc{i}.leave;\n".format( + indent=indent, i=i) -for i in range(n): - transitions += "{:s}{{ proc{:d}={{allowed}} }}: green -> red : proc{:d}.req;\n".format(8*" ", i, i) + # red -> red: no process leaves + no_leave = conj("~proc{i}.leave", range(n)) + for i in range(n): + transitions += "{indent}{{ proc{i}={{}} }}: red -> red : {guard};\n".format( + indent=indent, i=i, guard=no_leave) -no_req_cond = "~proc0.req" -for i in range(1, n): - no_req_cond += " AND ~proc{:d}.req".format(i) + out += CA_TEMPLATE.format(transitions=transitions) -for i in range(n): - transitions += "{:s}{{ proc{:d}={{}} }}: green -> green : {:s};\n".format(8*" ", i, no_req_cond) + # f1: each process can eventually enter + f1 = conj("EF( EX( proc{i}.in ) )", range(n)) + out += PROPERTY_TEMPLATE.format(name="f1", formula=f1) -for i in range(n): - transitions += "{:s}{{ proc{:d}={{}} }}: red -> green : proc{:d}.leave;\n".format(8*" ", i, i) + # f2: all processes can simultaneously approach + f2 = "EF( " + conj("proc{i}.approach", range(n)) + " )" + out += PROPERTY_TEMPLATE.format(name="f2", formula=f2) -no_leave_cond = "~proc0.leave" -for i in range(1, n): - no_leave_cond += " AND ~proc{:d}.leave".format(i) + # f3: mutual exclusion (knowledge) + others_not_in = conj("~proc{i}.in", range(1, n)) + f3 = "AG( proc0.in IMPLIES K[proc0]({}) )".format(others_not_in) + out += PROPERTY_TEMPLATE.format(name="f3", formula=f3) -for i in range(n): - transitions += "{:s}{{ proc{:d}={{}} }}: red -> red : {:s};\n".format(8*" ", i, no_leave_cond) + # f4: mutual exclusion (common knowledge) + all_agents = conj("proc{i}", range(n), sep=",") + f4 = "AG( proc0.in IMPLIES C[{}]({}) )".format(all_agents, others_not_in) + out += PROPERTY_TEMPLATE.format(name="f4", formula=f4) -out += CA_STR.format(transitions) + return out -## f1 -#formula = "EF( proc0.in )" -#out += PROPERTY_STR.format("f1",formula) -# f1 -formula = "EF( EX( proc0.in ) )" -for i in range(1, n): - formula += " AND EF( EX( proc{:d}.in ) )".format(i, i) -out += PROPERTY_STR.format("f1",formula) +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("n", type=int, help="number of processes (must be > 1)") + args = parser.parse_args() -# f2 -formula = "EF( proc0.approach" -for i in range(1, n): - formula += " AND proc{:d}.approach".format(i) -formula += " )" -out += PROPERTY_STR.format("f2",formula) + if args.n < 2: + parser.error("number of processes must be > 1") -# f3 -subf = "~proc1.in" -for i in range(2, n): - subf += " AND ~proc{:d}.in".format(i) -formula = "AG( proc0.in IMPLIES K[proc0]({:s}) )".format(subf) -out += PROPERTY_STR.format("f3",formula) + print(generate(args.n)) -# f4 -subf = "~proc1.in" -for i in range(2, n): - subf += " AND ~proc{:d}.in".format(i) -all_agents = "proc0" -for i in range(1, n): - all_agents += ",proc{:d}".format(i) -formula = "AG( proc0.in IMPLIES C[{:s}]({:s}) )".format(all_agents,subf) -out += PROPERTY_STR.format("f4",formula) - -print(out) +if __name__ == "__main__": + main() diff --git a/examples/bdd/generators/old_syntax/gen_abstract.py b/examples/bdd/generators/old_syntax/gen_abstract.py new file mode 100644 index 0000000..f7302ce --- /dev/null +++ b/examples/bdd/generators/old_syntax/gen_abstract.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +""" +Generator for the Abstract Pipeline benchmark. + +Produces a reaction system modelling a pipeline of N modules, +each cycling through entities a -> y,b -> c -> d before passing +activation to the next module. The final reaction requires all +modules to have completed (all y_i present). + +NOTE: generates old-syntax input (context-entities, initial-contexts, +rsctl-property) which is not compatible with the current parser. +""" + +import argparse + + +def generate(n, variant): + out = "reactions {\n" + out += "\t{ {x},{s} -> {a1} };\n" + + for i in range(1, n + 1): + out += "\t{{ {{a{i}}},{{s}} -> {{y{i}}} }};\n".format(i=i) + out += "\t{{ {{a{i}}},{{s}} -> {{b{i}}} }};\n".format(i=i) + out += "\t{{ {{b{i}}},{{s}} -> {{c{i}}} }};\n".format(i=i) + out += "\t{{ {{c{i}}},{{s}} -> {{d{i}}} }};\n".format(i=i) + out += "\t{{ {{d{i},y{i}}},{{s}} -> {{a{j}}} }};\n".format(i=i, j=i + 1) + out += "\t{{ {{y{i}}},{{s}} -> {{y{i}}} }};\n".format(i=i) + + final_reactants = "a" + str(n + 1) + for i in range(1, n + 1): + final_reactants += ",y" + str(i) + out += "\t{{ {{{r}}},{{s}} -> {{r}} }};\n".format(r=final_reactants) + out += "}\n" + + if variant == 1: + out += "context-entities { s }\n" + elif variant == 2: + ctx = "s" + for i in range(1, n + 1): + if i % 2 == 0: + ctx += ",a" + str(i) + out += "context-entities {{ {} }}\n".format(ctx) + + out += "initial-contexts { {x} }\n" + out += "rsctl-property { E[{}]F(r) }\n" + + return out + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("n", type=int, help="number of pipeline modules") + parser.add_argument("variant", type=int, choices=[1, 2], + help="context variant (1: minimal, 2: extended)") + args = parser.parse_args() + + print(generate(args.n, args.variant)) + + +if __name__ == "__main__": + main() diff --git a/examples/bdd/generators/old_syntax/gen_bc.py b/examples/bdd/generators/old_syntax/gen_bc.py new file mode 100644 index 0000000..9941a89 --- /dev/null +++ b/examples/bdd/generators/old_syntax/gen_bc.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +""" +Generator for the Binary Counter (BC) benchmark. + +Produces a reaction system modelling an N-bit binary counter +with increment and decrement operations controlled via context +entities. Generates one of four RSCTL properties. + +NOTE: generates old-syntax input (context-entities, initial-contexts, +rsctl-property) which is not compatible with the current parser. +""" + +import argparse + +K = 8 # constant used by property 3 + + +def generate(n, prop): + out = "reactions {\n" + + # (1) no dec, no inc: bits persist + out += "\t# (1) no decrement, no increment\n" + for j in range(n): + out += "\t{{{{p{j}}},{{dec,inc}} -> {{p{j}}}}};\n".format(j=j) + + # (2) increment operation + out += "\n\t# (2) increment operation\n" + out += "\t{{inc},{dec,p0} -> {p0}};\n" + for j in range(1, n): + bits = ",".join("p" + str(k) for k in range(j)) + out += "\t{{{{inc,{bits}}},{{dec,p{j}}} -> {{p{j}}}}};\n".format(bits=bits, j=j) + + out += "\n\t# the more significant bits remain (inc)\n" + for j in range(n): + for k in range(j + 1, n): + out += "\t{{{{inc,p{k}}},{{dec,p{j}}} -> {{p{k}}}}};\n".format(j=j, k=k) + + # (3) decrement operation + out += "\n\t# (3) decrement operation\n" + for j in range(n): + bits = ",".join("p" + str(k) for k in range(j + 1)) + out += "\t{{{{dec}},{{inc,{bits}}} -> {{p{j}}}}};\n".format(bits=bits, j=j) + + out += "\n\t# the more significant bits remain (dec)\n" + for j in range(n): + for k in range(j + 1, n): + out += "\t{{{{dec,p{j},p{k}}},{{inc}} -> {{p{k}}}}};\n".format(j=j, k=k) + + out += "}\n\n" + out += "context-entities { inc,dec }\n" + out += "initial-contexts { {} }\n" + + if prop == 1: + all_neg = " AND ".join("~p" + str(i) for i in range(n)) + all_pos = " AND ".join("p" + str(i) for i in range(n)) + out += "rsctl-property {{ AG(({}) IMPLIES E[{{inc}},{{dec}}]F({})) }}\n".format( + all_neg, all_pos) + elif prop == 2: + all_pos = " AND ".join("p" + str(i) for i in range(n)) + all_neg = " AND ".join("~p" + str(i) for i in range(n)) + out += "rsctl-property {{ AG(({}) IMPLIES A[{{inc}}]X({})) }}\n".format( + all_pos, all_neg) + elif prop == 3: + parts = ["p" + str(i) for i in range(n - K)] + parts += ["~p" + str(i) for i in range(n - K, n)] + out += "rsctl-property {{ E[{{inc}}]F({}) }}\n".format(" AND ".join(parts)) + elif prop == 4: + out += "rsctl-property {{ AG( p{} IMPLIES EF ~p{} ) }}\n".format(n - 1, n - 1) + + return out + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("n", type=int, help="number of bits") + parser.add_argument("property", type=int, choices=[1, 2, 3, 4], + help="property to generate (1-4)") + args = parser.parse_args() + + if args.property == 3 and args.n < K + 1: + parser.error("n must be >= {} for property 3".format(K + 1)) + + print(generate(args.n, args.property)) + + +if __name__ == "__main__": + main() diff --git a/examples/bdd/generators/old_syntax/gen_mutex.py b/examples/bdd/generators/old_syntax/gen_mutex.py new file mode 100644 index 0000000..4027446 --- /dev/null +++ b/examples/bdd/generators/old_syntax/gen_mutex.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +""" +Generator for the Mutual Exclusion (Mutex) benchmark. + +Produces a reaction system modelling N processes competing for +exclusive access to a critical section, using activation signals, +request tokens, and a lock. Generates one of three RSCTL properties. + +NOTE: generates old-syntax input (context-entities, initial-contexts, +rsctl-property) which is not compatible with the current parser. +""" + +import argparse + + +def generate(n, formula): + out = "reactions {\n" + for i in range(n): + out += "\t{{{{out{i},act{i}}},{{}} -> {{request{i}}}}};\n".format(i=i) + out += "\t{{{{out{i}}},{{act{i}}} -> {{out{i}}}}};\n".format(i=i) + + for j in range(n): + if i != j: + out += "\t{{{{request{i},act{i},act{j}}},{{}} -> {{request{i}}}}};\n".format( + i=i, j=j) + + out += "\t{{{{request{i}}},{{act{i}}} -> {{request{i}}}}};\n".format(i=i) + + other_acts = ",".join("act" + str(j) for j in range(n) if i != j) + out += "\t{{{{request{i},act{i}}},{{{others},lock}} -> {{in{i},lock}}}};\n".format( + i=i, others=other_acts) + + out += "\t{{{{in{i},act{i}}},{{}} -> {{out{i},done}}}};\n".format(i=i) + out += "\t{{{{in{i}}},{{act{i}}} -> {{in{i}}}}};\n".format(i=i) + + out += "\t{{lock},{done} -> {lock}};\n" + out += "}\n" + + ctx_ents = ",".join("act" + str(i) for i in range(n)) + out += "context-entities {{ {} }}\n".format(ctx_ents) + + init_ents = ",".join("out" + str(i) for i in range(n)) + out += "initial-contexts {{ {{{}}} }}\n".format(init_ents) + + if formula == 1: + out += "rsctl-property { A[{act1}]F(in1) }\n" + elif formula == 2: + out += "rsctl-property { E[{act1}]F(in1) }\n" + elif formula == 3: + pairs = [] + for i in range(n): + for j in range(i + 1, n): + pairs.append("~(in{} AND in{})".format(i, j)) + out += "rsctl-property {{ AG({}) }}\n".format(" AND ".join(pairs)) + + return out + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("n", type=int, help="number of processes") + parser.add_argument("formula", type=int, choices=[1, 2, 3], + help="formula to generate (1: A[]F, 2: E[]F, 3: AG mutex)") + args = parser.parse_args() + + print(generate(args.n, args.formula)) + + +if __name__ == "__main__": + main() diff --git a/examples/bdd/tgc.rs b/examples/bdd/tgc.drs similarity index 100% rename from examples/bdd/tgc.rs rename to examples/bdd/tgc.drs diff --git a/examples/bdd/tgc4.drs b/examples/bdd/tgc4.drs new file mode 100644 index 0000000..ed9b345 --- /dev/null +++ b/examples/bdd/tgc4.drs @@ -0,0 +1,70 @@ + +options { use-context-automaton; make-progressive; }; +reactions { + + proc0 { + {{out}, {} -> {approach}}; + {{approach}, {req} -> {req}}; + {{allowed}, {} -> {in}}; + {{in}, {} -> {out,leave}}; + {{req}, {in} -> {req}}; + }; + + proc1 { + {{out}, {} -> {approach}}; + {{approach}, {req} -> {req}}; + {{allowed}, {} -> {in}}; + {{in}, {} -> {out,leave}}; + {{req}, {in} -> {req}}; + }; + + proc2 { + {{out}, {} -> {approach}}; + {{approach}, {req} -> {req}}; + {{allowed}, {} -> {in}}; + {{in}, {} -> {out,leave}}; + {{req}, {in} -> {req}}; + }; + + proc3 { + {{out}, {} -> {approach}}; + {{approach}, {req} -> {req}}; + {{allowed}, {} -> {in}}; + {{in}, {} -> {out,leave}}; + {{req}, {in} -> {req}}; + }; +}; + +context-automaton { + states { init, green, red }; + init-state { init }; + transitions { + { proc0={out} proc1={out} proc2={out} proc3={out} }: init -> green; + { proc0={allowed} }: green -> red : proc0.req; + { proc1={allowed} }: green -> red : proc1.req; + { proc2={allowed} }: green -> red : proc2.req; + { proc3={allowed} }: green -> red : proc3.req; + { proc0={} }: green -> green : ~proc0.req AND ~proc1.req AND ~proc2.req AND ~proc3.req; + { proc1={} }: green -> green : ~proc0.req AND ~proc1.req AND ~proc2.req AND ~proc3.req; + { proc2={} }: green -> green : ~proc0.req AND ~proc1.req AND ~proc2.req AND ~proc3.req; + { proc3={} }: green -> green : ~proc0.req AND ~proc1.req AND ~proc2.req AND ~proc3.req; + { proc0={} }: red -> green : proc0.leave; + { proc1={} }: red -> green : proc1.leave; + { proc2={} }: red -> green : proc2.leave; + { proc3={} }: red -> green : proc3.leave; + { proc0={} }: red -> red : ~proc0.leave AND ~proc1.leave AND ~proc2.leave AND ~proc3.leave; + { proc1={} }: red -> red : ~proc0.leave AND ~proc1.leave AND ~proc2.leave AND ~proc3.leave; + { proc2={} }: red -> red : ~proc0.leave AND ~proc1.leave AND ~proc2.leave AND ~proc3.leave; + { proc3={} }: red -> red : ~proc0.leave AND ~proc1.leave AND ~proc2.leave AND ~proc3.leave; + + }; +}; + +rsctlk-property { f1 : EF( EX( proc0.in ) ) AND EF( EX( proc1.in ) ) AND EF( EX( proc2.in ) ) AND EF( EX( proc3.in ) ) }; + +rsctlk-property { f2 : EF( proc0.approach AND proc1.approach AND proc2.approach AND proc3.approach ) }; + +rsctlk-property { f3 : AG( proc0.in IMPLIES K[proc0](~proc1.in AND ~proc2.in AND ~proc3.in) ) }; + +rsctlk-property { f4 : AG( proc0.in IMPLIES C[proc0,proc1,proc2,proc3](~proc1.in AND ~proc2.in AND ~proc3.in) ) }; + diff --git a/reactics-bdd/ctx_aut.cc b/reactics-bdd/ctx_aut.cc index fb7c9f7..7509c7e 100644 --- a/reactics-bdd/ctx_aut.cc +++ b/reactics-bdd/ctx_aut.cc @@ -13,12 +13,7 @@ CtxAut::CtxAut(Options *opts, RctSys *parent_rctsys) bool CtxAut::hasState(std::string name) { - if (states_names.find(name) == states_names.end()) { - return false; - } - else { - return true; - } + return states_names.find(name) != states_names.end(); } State CtxAut::getStateID(std::string name) diff --git a/reactics-bdd/ctx_aut.hh b/reactics-bdd/ctx_aut.hh index d446459..a91a326 100644 --- a/reactics-bdd/ctx_aut.hh +++ b/reactics-bdd/ctx_aut.hh @@ -12,10 +12,7 @@ #include #include #include -// #include "rs.hh" #include "types.hh" -// #include "options.hh" -// #include "stateconstr.hh" using std::cout; using std::endl; diff --git a/reactics-bdd/drs_benchmark.sh b/reactics-bdd/drs_benchmark.sh index b7ad43d..ef2e2c6 100755 --- a/reactics-bdd/drs_benchmark.sh +++ b/reactics-bdd/drs_benchmark.sh @@ -1,6 +1,6 @@ #!/bin/sh -TMPINPUT="tmp_$RANDOM$RANDOM.rs" +TMPINPUT="tmp_$RANDOM$RANDOM.drs" CMD="./reactics -B" diff --git a/reactics-bdd/formrsctlk.cc b/reactics-bdd/formrsctlk.cc index 4658e52..d203849 100644 --- a/reactics-bdd/formrsctlk.cc +++ b/reactics-bdd/formrsctlk.cc @@ -172,39 +172,6 @@ bool FormRSCTLK::isERSCTLK(void) const std::string FormRSCTLK::getActionsStr(void) const { - // if (actions != nullptr) { - // std::string r = "[ "; - // bool firstact = true; - // - // for (ActionsVec_f::iterator act = actions->begin(); act != actions->end(); - // ++act) { - // if (!firstact) { - // r += ","; - // } - // else { - // firstact = false; - // } - // - // r += "{"; - // bool firstent = true; - // - // for (Action_f::iterator ent = act->begin(); ent != act->end(); ++ent) { - // if (!firstent) { - // r += ","; - // } - // else { - // firstent = false; - // } - // - // r += *ent; - // } - // - // r += "}"; - // } - // - // r += " ]"; - // return r; - // } if (boolCtx != nullptr) { return "< " + boolCtx->toStr() + " >"; } @@ -222,22 +189,6 @@ void FormRSCTLK::encodeActions(const SymRS *srs) actions_bdd = new BDD(srs->getBDDfalse()); assert(boolCtx != nullptr); - // assert(actions != nullptr || boolCtx != nullptr); - // assert(!(actions != nullptr && boolCtx != nullptr)); - - // if (actions != nullptr) { - // for (ActionsVec_f::iterator act = actions->begin(); act != actions->end(); - // ++act) { - // BDD single_action = srs->getBDDtrue(); - // - // for (Action_f::iterator ent = act->begin(); ent != act->end(); ++ent) { - // single_action *= srs->encActStrEntity(*ent); - // } - // - // single_action = srs->compContext(single_action); - // *actions_bdd += single_action; - // } - // } if (boolCtx != nullptr) { *actions_bdd = boolCtx->getBDDforContext(srs); } diff --git a/reactics-bdd/formrsctlk.hh b/reactics-bdd/formrsctlk.hh index b9091fd..c9f557f 100644 --- a/reactics-bdd/formrsctlk.hh +++ b/reactics-bdd/formrsctlk.hh @@ -9,7 +9,6 @@ #include #include #include -// #include "rs.hh" #include "symrs.hh" #include "cudd.hh" #include "types.hh" @@ -56,9 +55,6 @@ using std::cout; using std::endl; -// typedef std::string Entity_f; -// typedef std::set Action_f; -// typedef vector ActionsVec_f; typedef std::set Agents_f; class StateConstr; @@ -74,7 +70,6 @@ class FormRSCTLK std::string proc_name; bool tf; BDD *bdd; - // ActionsVec_f *actions; BDD *actions_bdd; StateConstr *boolCtx; Agents_f agents; @@ -93,7 +88,6 @@ class FormRSCTLK arg[0] = nullptr; arg[1] = nullptr; bdd = nullptr; - // actions = nullptr; actions_bdd = nullptr; boolCtx = nullptr; } @@ -110,7 +104,6 @@ class FormRSCTLK arg[0] = nullptr; arg[1] = nullptr; bdd = nullptr; - // actions = nullptr; actions_bdd = nullptr; boolCtx = nullptr; } @@ -126,28 +119,10 @@ class FormRSCTLK arg[0] = form1; arg[1] = form2; bdd = nullptr; - // actions = nullptr; actions_bdd = nullptr; boolCtx = nullptr; } - /** - * @brief Constructor for two-argument formula with action restrictions. - */ - // FormRSCTLK(Oper op, ActionsVec_f *acts, FormRSCTLK *form1, FormRSCTLK *form2) - // { - // assert(acts != nullptr); - // assert(RSCTLK_COND_2ARG(op)); - // assert(RSCTLK_COND_ACT(op)); - // oper = op; - // arg[0] = form1; - // arg[1] = form2; - // bdd = nullptr; - // actions = acts; - // actions_bdd = nullptr; - // boolCtx = nullptr; - // } - /** * @brief Constructor for two-argument formula with Boolean context restrictions. */ @@ -160,7 +135,6 @@ class FormRSCTLK arg[0] = form1; arg[1] = form2; bdd = nullptr; - // actions = nullptr; actions_bdd = nullptr; boolCtx = bctx; } @@ -176,28 +150,10 @@ class FormRSCTLK arg[0] = form1; arg[1] = nullptr; bdd = nullptr; - // actions = nullptr; actions_bdd = nullptr; boolCtx = nullptr; } - /** - * @brief Constructor for one-argument formula with action restrictions. - */ - // FormRSCTLK(Oper op, ActionsVec_f *acts, FormRSCTLK *form1) - // { - // assert(acts != nullptr); - // assert(RSCTLK_COND_1ARG(op)); - // assert(RSCTLK_COND_ACT(op)); - // oper = op; - // arg[0] = form1; - // arg[1] = nullptr; - // bdd = nullptr; - // // actions = acts; - // actions_bdd = nullptr; - // boolCtx = nullptr; - // } - /** * @brief Constructor for one-argument formula with Boolean context restrictions. */ @@ -210,7 +166,6 @@ class FormRSCTLK arg[0] = form1; arg[1] = nullptr; bdd = nullptr; - // actions = nullptr; actions_bdd = nullptr; boolCtx = bctx; } @@ -234,7 +189,6 @@ class FormRSCTLK delete arg[0]; delete arg[1]; delete bdd; - // delete actions; delete actions_bdd; delete boolCtx; } diff --git a/reactics-bdd/in/hsr.rs b/reactics-bdd/in/hsr.drs similarity index 100% rename from reactics-bdd/in/hsr.rs rename to reactics-bdd/in/hsr.drs diff --git a/reactics-bdd/in/hsr_ca.rs b/reactics-bdd/in/hsr_ca.drs similarity index 100% rename from reactics-bdd/in/hsr_ca.rs rename to reactics-bdd/in/hsr_ca.drs diff --git a/reactics-bdd/in/hsr_drs.rs b/reactics-bdd/in/hsr_drs.drs similarity index 100% rename from reactics-bdd/in/hsr_drs.rs rename to reactics-bdd/in/hsr_drs.drs diff --git a/reactics-bdd/in/old_syntax/bc32.rs b/reactics-bdd/in/old_syntax/bc32.drs similarity index 100% rename from reactics-bdd/in/old_syntax/bc32.rs rename to reactics-bdd/in/old_syntax/bc32.drs diff --git a/reactics-bdd/in/old_syntax/bc8.rs b/reactics-bdd/in/old_syntax/bc8.drs similarity index 100% rename from reactics-bdd/in/old_syntax/bc8.rs rename to reactics-bdd/in/old_syntax/bc8.drs diff --git a/reactics-bdd/in/old_syntax/coffee.rs b/reactics-bdd/in/old_syntax/coffee.drs similarity index 100% rename from reactics-bdd/in/old_syntax/coffee.rs rename to reactics-bdd/in/old_syntax/coffee.drs diff --git a/reactics-bdd/in/old_syntax/expr.rs b/reactics-bdd/in/old_syntax/expr.drs similarity index 100% rename from reactics-bdd/in/old_syntax/expr.rs rename to reactics-bdd/in/old_syntax/expr.drs diff --git a/reactics-bdd/in/old_syntax/simple_I1.rs b/reactics-bdd/in/old_syntax/simple_I1.drs similarity index 100% rename from reactics-bdd/in/old_syntax/simple_I1.rs rename to reactics-bdd/in/old_syntax/simple_I1.drs diff --git a/reactics-bdd/in/old_syntax/test.rs b/reactics-bdd/in/old_syntax/test.drs similarity index 100% rename from reactics-bdd/in/old_syntax/test.rs rename to reactics-bdd/in/old_syntax/test.drs diff --git a/reactics-bdd/in/old_syntax/u1.rs b/reactics-bdd/in/old_syntax/u1.drs similarity index 100% rename from reactics-bdd/in/old_syntax/u1.rs rename to reactics-bdd/in/old_syntax/u1.drs diff --git a/reactics-bdd/in/old_syntax/u2.rs b/reactics-bdd/in/old_syntax/u2.drs similarity index 100% rename from reactics-bdd/in/old_syntax/u2.rs rename to reactics-bdd/in/old_syntax/u2.drs diff --git a/reactics-bdd/in/scripts/bench_bc.sh b/reactics-bdd/in/scripts/bench_bc.sh index 5335a77..23cd961 100644 --- a/reactics-bdd/in/scripts/bench_bc.sh +++ b/reactics-bdd/in/scripts/bench_bc.sh @@ -42,10 +42,10 @@ for form in $forms; do echo "$x" > $filename - ./gen_bc.py $n $form > tmp.rs + ./gen_bc.py $n $form > tmp.drs - echo "EXEC: $tool $options $popt tmp.rs" - $tool $options $popt tmp.rs >&1 >> $filename + echo "EXEC: $tool $options $popt tmp.drs" + $tool $options $popt tmp.drs >&1 >> $filename result="$(tail -1 $filename | grep -E '.*;.*;.*;.*'| sed "s/STAT/$n /")" if [ "$result" = "" ];then diff --git a/reactics-bdd/in/scripts/bench_mutex.sh b/reactics-bdd/in/scripts/bench_mutex.sh index 822d4ab..b074516 100644 --- a/reactics-bdd/in/scripts/bench_mutex.sh +++ b/reactics-bdd/in/scripts/bench_mutex.sh @@ -44,11 +44,11 @@ for form in $forms; do echo "$x" > $filename - ./gen_mutex.py $n $form > tmp.rs + ./gen_mutex.py $n $form > tmp.drs - echo "EXEC: $tool $options $popt tmp.rs" + echo "EXEC: $tool $options $popt tmp.drs" - $tool $options $popt tmp.rs >&1 >> $filename + $tool $options $popt tmp.drs >&1 >> $filename result="$(tail -1 $filename | grep -E '.*;.*;.*;.*'| sed "s/STAT/$n /")" if [ "$result" = "" ];then diff --git a/reactics-bdd/in/scripts/bench_pipe.sh b/reactics-bdd/in/scripts/bench_pipe.sh index a527942..31f0e8b 100644 --- a/reactics-bdd/in/scripts/bench_pipe.sh +++ b/reactics-bdd/in/scripts/bench_pipe.sh @@ -43,11 +43,11 @@ for form in $forms; do echo "$x" > $filename - ./gen_abstract1.py $n $form > tmp.rs + ./gen_abstract1.py $n $form > tmp.drs - echo "EXEC: $tool $options $popt tmp.rs" + echo "EXEC: $tool $options $popt tmp.drs" - $tool $options $popt tmp.rs >&1 >> $filename + $tool $options $popt tmp.drs >&1 >> $filename result="$(tail -1 $filename | grep -E '.*;.*;.*;.*'| sed "s/STAT/$n /")" if [ "$result" = "" ];then diff --git a/reactics-bdd/in/scripts/benchmark.sh b/reactics-bdd/in/scripts/benchmark.sh index 61efef1..7798bab 100644 --- a/reactics-bdd/in/scripts/benchmark.sh +++ b/reactics-bdd/in/scripts/benchmark.sh @@ -4,8 +4,8 @@ for x in `seq 2 1 24`;do echo $y $x filename="results/f${y}_n${x}.out" echo "$x" > $filename - ./gen_bc.py $x $y > tmp.rs - ../main -c -B tmp.rs >> $filename + ./gen_bc.py $x $y > tmp.drs + ../main -c -B tmp.drs >> $filename result="$(tail -1 $filename | sed "s/STAT/$x /")" echo $result >> results/summary_f${y}.out echo $result @@ -13,4 +13,4 @@ for x in `seq 2 1 24`;do done done -rm tmp.rs +rm tmp.drs diff --git a/reactics-bdd/in/scripts/benchmark_abs1.sh b/reactics-bdd/in/scripts/benchmark_abs1.sh index 6089667..6b3f85b 100644 --- a/reactics-bdd/in/scripts/benchmark_abs1.sh +++ b/reactics-bdd/in/scripts/benchmark_abs1.sh @@ -4,8 +4,8 @@ for x in `seq 1 60`;do echo $x filename="results/abs_v${y}_f0_n${x}.out" echo "$x" > $filename - ./gen_abstract1.py $x $y > tmp.rs - ../main -z -c -p -v -B tmp.rs >&1 >> $filename + ./gen_abstract1.py $x $y > tmp.drs + ../main -z -c -p -v -B tmp.drs >&1 >> $filename result="$(tail -1 $filename | sed "s/STAT/$x /")" echo $result >> results/summary_v${y}_abs_f0.out echo $result @@ -13,4 +13,4 @@ for x in `seq 1 60`;do done done -rm tmp.rs +rm tmp.drs diff --git a/reactics-bdd/in/scripts/benchmark_abs1_PT.sh b/reactics-bdd/in/scripts/benchmark_abs1_PT.sh index 4f40afe..c69b705 100644 --- a/reactics-bdd/in/scripts/benchmark_abs1_PT.sh +++ b/reactics-bdd/in/scripts/benchmark_abs1_PT.sh @@ -4,8 +4,8 @@ for x in `seq 1 60`;do echo $x filename="results/abs_v${y}_f0_n${x}_PT.out" echo "$x" > $filename - ./gen_abstract1.py $x $y > tmp.rs - ../main -z -x -c -p -v -B tmp.rs >&1 >> $filename + ./gen_abstract1.py $x $y > tmp.drs + ../main -z -x -c -p -v -B tmp.drs >&1 >> $filename result="$(tail -1 $filename | sed "s/STAT/$x /")" echo $result >> results/summary_v${y}_abs_f0_PT.out echo $result @@ -13,4 +13,4 @@ for x in `seq 1 60`;do done done -rm tmp.rs +rm tmp.drs diff --git a/reactics-bdd/in/scripts/benchmark_bc.sh b/reactics-bdd/in/scripts/benchmark_bc.sh index f1c5231..acae1a4 100644 --- a/reactics-bdd/in/scripts/benchmark_bc.sh +++ b/reactics-bdd/in/scripts/benchmark_bc.sh @@ -4,8 +4,8 @@ for x in `seq 1 50`;do echo $x $y filename="results/bc_f${y}_n${x}.out" echo "$x" > $filename - ./gen_bc.py $x $y > tmp.rs - ../main -z -c -v -B tmp.rs >&1 >> $filename + ./gen_bc.py $x $y > tmp.drs + ../main -z -c -v -B tmp.drs >&1 >> $filename result="$(tail -1 $filename | sed "s/STAT/$x /")" echo $result >> results/summary_bc_f${y}.out echo $result @@ -13,4 +13,4 @@ for x in `seq 1 50`;do done done -rm tmp.rs +rm tmp.drs diff --git a/reactics-bdd/in/scripts/benchmark_mutex.sh b/reactics-bdd/in/scripts/benchmark_mutex.sh index e3cca35..2853dc7 100644 --- a/reactics-bdd/in/scripts/benchmark_mutex.sh +++ b/reactics-bdd/in/scripts/benchmark_mutex.sh @@ -4,8 +4,8 @@ for x in `seq 2 40`;do echo $x $y filename="results/mutex_f${y}_n${x}.out" echo "$x" > $filename - ./gen_mutex.py $x $y > tmp.rs - ../main -z -c -p -v -B tmp.rs >&1 >> $filename + ./gen_mutex.py $x $y > tmp.drs + ../main -z -c -p -v -B tmp.drs >&1 >> $filename result="$(tail -1 $filename | sed "s/STAT/$x /")" echo $result >> results/summary_mutex_f${y}.out echo $result @@ -13,4 +13,4 @@ for x in `seq 2 40`;do done done -rm tmp.rs +rm tmp.drs diff --git a/reactics-bdd/in/scripts/benchmark_mutex_PT.sh b/reactics-bdd/in/scripts/benchmark_mutex_PT.sh index 096b672..fba41b3 100644 --- a/reactics-bdd/in/scripts/benchmark_mutex_PT.sh +++ b/reactics-bdd/in/scripts/benchmark_mutex_PT.sh @@ -4,8 +4,8 @@ for x in `seq 2 40`;do echo $x $y filename="results/mutex_f${y}_n${x}_PT.out" echo "$x" > $filename - ./gen_mutex.py $x $y > tmp.rs - ../main -zcpxvB tmp.rs >&1 >> $filename + ./gen_mutex.py $x $y > tmp.drs + ../main -zcpxvB tmp.drs >&1 >> $filename result="$(tail -1 $filename | sed "s/STAT/$x /")" echo $result >> results/summary_mutex_f${y}_PT.out echo $result @@ -13,4 +13,4 @@ for x in `seq 2 40`;do done done -rm tmp.rs +rm tmp.drs diff --git a/reactics-bdd/in/scripts/gen_abstract1.py b/reactics-bdd/in/scripts/gen_abstract1.py deleted file mode 100755 index 8112ae8..0000000 --- a/reactics-bdd/in/scripts/gen_abstract1.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python - -from sys import argv,exit - -if len(argv) < 3: - print "Usage:", argv[0], " " - exit(100) - -n = int(argv[1]) -v = int(argv[2]) - -if not ( v > 0 and v < 3 ): - print "unsupported variant" - exit(100) - -print "reactions {" -s = "\t{ {x},{s} -> {a1} };\n" -print s - -for i in range(1,n+1): - s = "\t{ {a" + str(i) + "},{s} -> {y" + str(i) + "} };\n" - s += "\t{ {a" + str(i) + "},{s} -> {b" + str(i) + "} };\n" - s += "\t{ {b" + str(i) + "},{s} -> {c" + str(i) + "} };\n" - s += "\t{ {c" + str(i) + "},{s} -> {d" + str(i) + "} };\n" - s += "\t{ {d" + str(i) + ",y" + str(i) + "},{s} -> {a" + str(i+1) + "} };\n" - s += "\t{ {y" + str(i) + "},{s} -> {y" + str(i) + "} };\n" - print s - -s = "\t{ {a" + str(n+1) -for i in range(1,n+1): - s += ",y" + str(i) -s += "},{s} -> {r} };\n" -print s -print "}" - -if v == 1: - print "context-entities { s }" -elif v == 2: - s = "context-entities { s" - for i in range(1,n+1): - if i % 2 == 0: - s += ",a" + str(i) - s += " }" - print s - -print "initial-contexts { {x} }" -print "rsctl-property { E[{}]F(r) }" diff --git a/reactics-bdd/in/scripts/gen_asm.py b/reactics-bdd/in/scripts/gen_asm.py deleted file mode 100755 index a83a4e6..0000000 --- a/reactics-bdd/in/scripts/gen_asm.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env python - -from sys import argv,exit - -OPTIONS_STR = """ -options { use-context-automaton; make-progressive; }; -""" - -PROC_STR = """ - proc{:d} {{ - {{{{a}}, {{s}} -> {{y}}}}; - {{{{y}}, {{s}} -> {{y}}}}; - {{{{a}}, {{s}} -> {{b}}}}; - {{{{b}}, {{s}} -> {{c}}}}; - {{{{c}}, {{s}} -> {{d}}}}; - {{{{d,y}}, {{s}} -> {{dy}}}}; - }}; -""" - -FINAL_PROC = """ - procFinal { - {{done}, {s} -> {done}}; - }; -""" - -CA_STR = """ -context-automaton {{ - states {{ init, act }}; - init-state {{ init }}; - transitions {{ -{:s} - }}; -}}; -""" - -PROPERTY_STR = """ -rsctlk-property {{ {:s} : {:s} }}; -""" - - -################################################################# - -if len(argv) < 1: - print("Usage: {:s} ".format(argv[0])) - exit(100) - -n = int(argv[1]) - -assert n > 1, "number of proc must be > 1" - -out = "" - -out += OPTIONS_STR -out += "reactions {\n" -for i in range(1, n+1): - out += PROC_STR.format(i) - -out += FINAL_PROC -out += "};\n" - -transitions = "" - -init_trans = 8*" " + "{ proc1={a} }: init -> act;\n" -transitions += init_trans - -for i in range(1, n+1): - transitions += "{:s}{{ proc{:d}={{}} }}: act -> act : ~proc{:d}.dy;\n".format(8*" ", i, i, i) - -for i in range(2, n+1): - transitions += "{:s}{{ proc{:d}={{a}} }}: act -> act : proc{:d}.dy;\n".format(8*" ", i, i-1) - -final_cond = "proc1.dy" -for i in range(2, n+1): - final_cond += " AND proc{:d}.dy".format(i) - -transitions += "{:s}{{ procFinal={{done}} }}: act -> act : {:s};\n".format(8*" ", final_cond) - -out += CA_STR.format(transitions) - -# f1 -formula = "EF( procFinal.done )" -out += PROPERTY_STR.format("f1",formula) - -# f2 -formula = "AG( proc{:d}.d IMPLIES K[proc{:d}]( proc{:d}.y ) )".format(n, n, n-1) -out += PROPERTY_STR.format("f2",formula) - -# x1 -formula = "EF( ~procFinal.done )" -out += PROPERTY_STR.format("x1",formula) - -print(out) - diff --git a/reactics-bdd/in/scripts/gen_bc.py b/reactics-bdd/in/scripts/gen_bc.py deleted file mode 100755 index 0e543ff..0000000 --- a/reactics-bdd/in/scripts/gen_bc.py +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env python2.7 - -from sys import argv,exit - - -if len(argv) < 3: - print "Usage:", argv[0], " " - exit(100) - -n = int(argv[1]) -property = int(argv[2]) - -K = 8 -if property == 3: - if n < K+1: - print "too small n" - exit(100) - -if not ( property >= 1 and property < 5 ): - print "property: 1-4" - exit(101) - -print "reactions {" - -# (1) no dec, no inc -print "\t# (1) no decrement, no increment" -for j in range(0,n): - print "\t{{p" + str(j) + "},{dec,inc} -> {p" + str(j) + "}};" - -# (2) increment -s = "\n\t# (2) increment operation\n" -s += "\t{{inc},{dec,p0} -> {p0}};\n" -for j in range(1,n): - s += "\t{{inc," - for k in range(0,j): - s += "p" + str(k) - if k < j-1: - s += "," - s += "},{dec,p" + str(j) + "} -> {p" + str(j) + "}};\n" - -print s - -print "\t# the more significant bits remain (inc)" -s = "" -for j in range(0,n): - for k in range(j+1,n): - s += "\t{{inc,p" + str(k) + "},{dec,p" + str(j) + "} -> {p" + str(k) + "}};\n" - -print s - -print "\t# (3) decrement operation" -s = "" -for j in range(0,n): - s += "\t{{dec},{inc," - for k in range(0,j+1): - s += "p" + str(k) - if k < j: - s += "," - s += "} -> {p" + str(j) + "}};\n" -print s - -print "\t# the more significant bits remain (dec)" -s = "" -for j in range(0,n): - for k in range(j+1,n): - s += "\t{{dec,p" + str(j) + ",p" + str(k) + "},{inc} -> {p" + str(k) + "}};\n" - -s += "}\n" -print s - -print "context-entities { inc,dec }" -print "initial-contexts { {} }" - -if property == 4: - print "rsctl-property { AG( p" + str(n-1) + " IMPLIES EF ~p" + str(n-1) + " ) }" -elif property == 1: - s = "rsctl-property { AG((" - for i in range(0,n): - s += "~p" + str(i) - if i < n-1: - s += " AND " - s += ") IMPLIES E[{inc},{dec}]F(" - for i in range(0,n): - s += "p" + str(i) - if i < n-1: - s += " AND " - - s += ")) }" - print s -elif property == 2: - s = "rsctl-property { AG((" - for i in range(0,n): - s += "p" + str(i) - if i < n-1: - s += " AND " - s += ") IMPLIES A[{inc}]X(" - for i in range(0,n): - s += "~p" + str(i) - if i < n-1: - s += " AND " - - s += ")) }" - print s - -elif property == 3: - s = "rsctl-property { E[{inc}]F(" - for i in range(0,n-K): - s += "p" + str(i) - if i < n-1: - s += " AND " - for i in range(n-K,n): - s += "~p" + str(i) - if i < n-1: - s += " AND " - s += ") }" - print s - - diff --git a/reactics-bdd/in/scripts/gen_mutex.py b/reactics-bdd/in/scripts/gen_mutex.py deleted file mode 100755 index 95337d5..0000000 --- a/reactics-bdd/in/scripts/gen_mutex.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python - -from sys import argv,exit - - -if len(argv) < 3: - print "Usage:", argv[0], " " - exit(100) - -n = int(argv[1]) -f = int(argv[2]) - -if not ( f>0 and f<4 ): - print "unsupported formula" - exit(100) - -print "reactions {" -for i in range(0,n): - s = "\t{{out" + str(i) + ",act" + str(i) + "},{} -> {request" + str(i) + "}};\n" - s += "\t{{out" + str(i) + "},{act" + str(i) + "} -> {out" + str(i) + "}};\n" - for j in range(0,n): - if i != j: - s += "\t{{request" + str(i) + ",act" + str(i) + ",act" + str(j) + "},{} -> {request" + str(i) + "}};\n" - #s += "\t{{request" + str(i) + "},{" - #for j in range(0,n): - # s += "act" + str(j) - # if j < n-1: - # s += "," - #s += "} -> {request" + str(i) + "}};\n" - s += "\t{{request" + str(i) + "},{act" + str(i) + "} -> {request" + str(i) + "}};\n" - s += "\t{{request" + str(i) + ",act" + str(i) + "},{" - for j in range(0,n): - if i != j: - s += "act" + str(j) + "," - s += "lock} -> {in" + str(i) + ",lock}};\n" - s += "\t{{in" + str(i) + ",act" + str(i) + "},{} -> {out" + str(i) + ",done}};\n" - s += "\t{{in" + str(i) + "},{act" + str(i) + "} -> {in" + str(i) + "}};\n" - print s - -print "\t{{lock},{done} -> {lock}};" - -print "}" - -s = "context-entities {" -for i in range(0,n): - s += "act" + str(i) - if i < n-1: - s += "," -s += "}\n" -print s - -s = "initial-contexts { {" -for i in range(0,n): - s += "out" + str(i) - if i < n-1: - s += "," -s += "} }\n" -print s - -#print "rsctl-property { A[{act1}]G(A[{act1}]F(in1)) }" -if f == 1: - print "rsctl-property { A[{act1}]F(in1) }" -elif f == 2: - print "rsctl-property { E[{act1}]F(in1) }" -elif f == 3: - s = "rsctl-property { AG(" - for i in range(0,n): - for j in range(i+1,n): - s += "~(in" + str(i) + " AND in" + str(j) + ")" - if i < n-2: - s += " AND " - s += ") }" - print s -else: - exit(111) - diff --git a/reactics-bdd/in/scripts/gen_tgc_sc.py b/reactics-bdd/in/scripts/gen_tgc_sc.py deleted file mode 100755 index d4bfcf0..0000000 --- a/reactics-bdd/in/scripts/gen_tgc_sc.py +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env python - -from sys import argv,exit - -OPTIONS_STR = """ -options { use-context-automaton; make-progressive; }; -""" - -PROC_STR = """ - proc{:d} {{ - {{{{out}}, {{}} -> {{approach}}}}; - {{{{approach}}, {{req}} -> {{req}}}}; - {{{{allowed}}, {{}} -> {{in}}}}; - {{{{in}}, {{}} -> {{out,leave}}}}; - {{{{req}}, {{in}} -> {{req}}}}; - }}; -""" - -CA_STR = """ -context-automaton {{ - states {{ init, green, red }}; - init-state {{ init }}; - transitions {{ -{:s} - }}; -}}; -""" - -PROPERTY_STR = """ -rsctlk-property {{ {:s} : {:s} }}; -""" - - -################################################################# - -if len(argv) < 1: - print("Usage: {:s} ".format(argv[0])) - exit(100) - -n = int(argv[1]) - -assert n > 1, "number of proc must be > 1" - -out = "" - -out += OPTIONS_STR -out += "reactions {\n" -for i in range(n): - out += PROC_STR.format(i) -out += "};\n" - -transitions = "" - -init_trans = 8*" " + "{ " -for i in range(n): - init_trans += "proc{:d}={{out}} ".format(i) -init_trans += "}: init -> green;\n" -transitions += init_trans - -for i in range(n): - transitions += "{:s}{{ proc{:d}={{allowed}} }}: green -> red : proc{:d}.req;\n".format(8*" ", i, i) - -no_req_cond = "~proc0.req" -for i in range(1, n): - no_req_cond += " AND ~proc{:d}.req".format(i) - -for i in range(n): - transitions += "{:s}{{ proc{:d}={{}} }}: green -> green : {:s};\n".format(8*" ", i, no_req_cond) - -for i in range(n): - transitions += "{:s}{{ proc{:d}={{}} }}: red -> green : proc{:d}.leave;\n".format(8*" ", i, i) - -no_leave_cond = "~proc0.leave" -for i in range(1, n): - no_leave_cond += " AND ~proc{:d}.leave".format(i) - -for i in range(n): - transitions += "{:s}{{ proc{:d}={{}} }}: red -> red : {:s};\n".format(8*" ", i, no_leave_cond) - -out += CA_STR.format(transitions) - -## f1 -#formula = "EF( proc0.in )" -#out += PROPERTY_STR.format("f1",formula) - -# f1 -formula = "EF( EX( proc0.in ) )" -for i in range(1, n): - formula += " AND EF( EX( proc{:d}.in ) )".format(i, i) -out += PROPERTY_STR.format("f1",formula) - -# f2 -formula = "EF( proc0.approach" -for i in range(1, n): - formula += " AND proc{:d}.approach".format(i) -formula += " )" -out += PROPERTY_STR.format("f2",formula) - -# f3 -subf = "~proc1.in" -for i in range(2, n): - subf += " AND ~proc{:d}.in".format(i) -formula = "AG( proc0.in IMPLIES K[proc0]({:s}) )".format(subf) -out += PROPERTY_STR.format("f3",formula) - -# f4 -subf = "~proc1.in" -for i in range(2, n): - subf += " AND ~proc{:d}.in".format(i) -all_agents = "proc0" -for i in range(1, n): - all_agents += ",proc{:d}".format(i) -formula = "AG( proc0.in IMPLIES C[{:s}]({:s}) )".format(all_agents,subf) -out += PROPERTY_STR.format("f4",formula) - -print(out) - diff --git a/reactics-bdd/in/trivial.rs b/reactics-bdd/in/trivial.drs similarity index 100% rename from reactics-bdd/in/trivial.rs rename to reactics-bdd/in/trivial.drs diff --git a/reactics-bdd/mc.cc b/reactics-bdd/mc.cc index d1c7b4c..19fb1c0 100644 --- a/reactics-bdd/mc.cc +++ b/reactics-bdd/mc.cc @@ -517,9 +517,9 @@ BDD ModelChecker::getIthOnly(Process proc_id) /* nothing has been found in the cache */ BDD bdd = BDD_TRUE; - for (auto i = 0; i < pv_drs_E->size(); ++i) { + for (size_t i = 0; i < pv_drs_E->size(); ++i) { - if (i == proc_id) { + if (i == static_cast(proc_id)) { continue; } @@ -591,13 +591,7 @@ bool ModelChecker::checkRSCTLKfull(FormRSCTLK *form) VERB("Checking the formula"); - //if (*initStates * getStatesRSCTLK(form) != cuddMgr->bddZero()) - if (*initStates * getStatesRSCTLK(form) == *initStates) { - result = true; - } - else { - result = false; - } + result = (*initStates * getStatesRSCTLK(form) == *initStates); cleanup(); diff --git a/reactics-bdd/reactics.cc b/reactics-bdd/reactics.cc index 90e1298..6cd731f 100644 --- a/reactics-bdd/reactics.cc +++ b/reactics-bdd/reactics.cc @@ -37,13 +37,13 @@ int main(int argc, char **argv) &option_index)) != -1) { switch (c) { case 0: - printf("option %s", long_options[option_index].name); + cout << "option " << long_options[option_index].name; if (optarg) { - printf(" with arg %s", optarg); + cout << " with arg " << optarg; } - printf("\n"); + cout << endl; if (strcmp(long_options[option_index].name, "trace-parsing")) { driver.trace_parsing = true; @@ -236,16 +236,7 @@ int main(int argc, char **argv) delete opts; - int ret_val; - - if (result) { - ret_val = 0; - } - else { - ret_val = 1; - } - - return ret_val; + return result ? 0 : 1; } void print_help(std::string path_str) diff --git a/reactics-bdd/rs.cc b/reactics-bdd/rs.cc index f04c578..8cfb547 100644 --- a/reactics-bdd/rs.cc +++ b/reactics-bdd/rs.cc @@ -14,12 +14,7 @@ RctSys::RctSys(void) bool RctSys::hasEntity(std::string name) { - if (entities_names.find(name) == entities_names.end()) { - return false; - } - else { - return true; - } + return entities_names.find(name) != entities_names.end(); } void RctSys::addEntity(std::string name) @@ -84,21 +79,12 @@ void RctSys::addProcess(std::string processName) bool RctSys::hasProcess(std::string processName) { - if (processes_names.find(processName) == processes_names.end()) { - return false; - } - else { - return true; - } + return processes_names.find(processName) != processes_names.end(); } bool RctSys::hasProcess(Process processID) { - if (processID >= processes_ids.size()) { - return false; - } - - return true; + return processID < processes_ids.size(); } Process RctSys::getProcessID(std::string processName) @@ -120,28 +106,26 @@ std::string RctSys::getProcessName(Process processID) } } -void RctSys::pushReactant(std::string entityName) +void RctSys::ensureEntity(std::string entityName) { if (!hasEntity(entityName)) { addEntity(entityName); } +} +void RctSys::pushReactant(std::string entityName) +{ + ensureEntity(entityName); tmpReactants.insert(getEntityID(entityName)); } void RctSys::pushInhibitor(std::string entityName) { - if (!hasEntity(entityName)) { - addEntity(entityName); - } - + ensureEntity(entityName); tmpInhibitors.insert(getEntityID(entityName)); } void RctSys::pushProduct(std::string entityName) { - if (!hasEntity(entityName)) { - addEntity(entityName); - } - + ensureEntity(entityName); tmpProducts.insert(getEntityID(entityName)); } @@ -232,10 +216,7 @@ void RctSys::commitInitState(void) void RctSys::addActionEntity(std::string entityName) { - if (!hasEntity(entityName)) { - addEntity(entityName); - } - + ensureEntity(entityName); actionEntities.insert(getEntityID(entityName)); } @@ -248,12 +229,7 @@ void RctSys::addActionEntity(Entity entity) bool RctSys::isActionEntity(Entity entity) { - if (actionEntities.count(entity) > 0) { - return true; - } - else { - return false; - } + return actionEntities.count(entity) > 0; } void RctSys::showActionEntities(void) diff --git a/reactics-bdd/rs.hh b/reactics-bdd/rs.hh index 27ace9b..68a0f54 100644 --- a/reactics-bdd/rs.hh +++ b/reactics-bdd/rs.hh @@ -50,6 +50,7 @@ class RctSys void addReactionForCurrentProcess(Reaction reaction); + void ensureEntity(std::string entityName); void pushReactant(std::string entityName); void pushInhibitor(std::string entityName); void pushProduct(std::string entityName); diff --git a/reactics-bdd/symrs.cc b/reactics-bdd/symrs.cc index d99b38a..61fa969 100644 --- a/reactics-bdd/symrs.cc +++ b/reactics-bdd/symrs.cc @@ -17,9 +17,6 @@ SymRS::SymRS(RctSys *rs, Options *opts) totalEntities = rs->getEntitiesSize(); - // TODO: remove - totalActions = 0; - totalRctSysStateVars = getTotalProductVariables(); totalCtxEntities = getTotalCtxEntitiesVariables(); @@ -404,39 +401,19 @@ BDD SymRS::encCtxEntity(Process proc_id, Entity entity) const bool SymRS::productEntityExists(Process proc_id, Entity entity) const { - if (prod_ent_local_idx.count(proc_id) == 0) { - return false; - } - else { - if (prod_ent_local_idx.at(proc_id).count(entity) == 1) { - return true; - } - } - - return false; + return prod_ent_local_idx.count(proc_id) != 0 && + prod_ent_local_idx.at(proc_id).count(entity) == 1; } bool SymRS::ctxEntityExists(Process proc_id, Entity entity) const { - if (ctx_ent_local_idx.count(proc_id) == 0) { - return false; - } - else { - if (ctx_ent_local_idx.at(proc_id).count(entity) == 1) { - return true; - } - } - - return false; + return ctx_ent_local_idx.count(proc_id) != 0 && + ctx_ent_local_idx.at(proc_id).count(entity) == 1; } bool SymRS::processUsesEntity(Process proc_id, Entity entity_id) const { - if (productEntityExists(proc_id, entity_id) || ctxEntityExists(proc_id, entity_id)) { - return true; - } - - return false; + return productEntityExists(proc_id, entity_id) || ctxEntityExists(proc_id, entity_id); } BDD SymRS::encEntitiesConj_raw(Process proc_id, const Entities &entities, bool succ) @@ -511,20 +488,6 @@ BDD SymRS::encContext(const EntitiesForProc &proc_entities) return r; } -BDD SymRS::compState(const BDD &state) const -{ - assert(0); - BDD s = state; - - for (unsigned int i = 0; i < totalRctSysStateVars; ++i) { - if (!(*pv)[i] * state != cuddMgr->bddZero()) { - s *= !(*pv)[i]; - } - } - - return s; -} - BDD SymRS::compContext(const BDD &context) const { BDD c = context; @@ -725,65 +688,6 @@ BDD SymRS::encEnabledness(Process prod_proc_id, Entity entity_id) return enab; } -// BDD SymRS::encEnabledness(Process prod_proc_id, Entity entity_id) -// { -// assert(prod_conds.size() > prod_proc_id); - -// BDD enab = BDD_FALSE; - -// auto production_conditions = prod_conds[prod_proc_id][entity_id]; - -// VERB_LN(5, "| Produce " << rs->getEntityName(entity_id) << " in " << rs->getProcessName(prod_proc_id) << ":"); - -// for (const auto &cond : production_conditions) { - -// BDD reactants = BDD_TRUE; -// BDD inhibitors = BDD_TRUE; - -// for (const auto &reactant : cond.rctt) { -// BDD proc_reactants = BDD_FALSE; - -// for (unsigned int proc_id = 0; proc_id < numberOfProc; ++proc_id) { -// if (processUsesEntity(proc_id, reactant)) { -// proc_reactants += encProcEnabled(proc_id) * encEntityCondition(proc_id, reactant); - -// VERB_LN(5, "| - if process " << rs->getProcessName(proc_id) << " is enabled and has " << rs->getEntityName(reactant)); - -// } -// } // END FOR: prod_id - -// reactants *= proc_reactants; -// } // END FOR: reactant - -// // For inhibitors, we take all the processes first and then we iterate over the inhibitors -// for (unsigned int proc_id = 0; proc_id < numberOfProc; ++proc_id) { -// BDD proc_inhibitors = BDD_TRUE; - -// for (const auto &inhibitor : cond.inhib) { -// if (processUsesEntity(proc_id, inhibitor)) { -// proc_inhibitors *= !encEntityCondition(proc_id, inhibitor); -// } -// } - -// if (proc_inhibitors != BDD_TRUE) { // just an optimisation -// proc_inhibitors += !encProcEnabled(proc_id); -// inhibitors *= proc_inhibitors; -// } -// } - -// enab += reactants * inhibitors; - -// } // END FOR: cond - -// if (opts->reorder_trans) { -// VERB_L2("Reordering"); -// Cudd_ReduceHeap(cuddMgr->getManager(), CUDD_REORDER_SIFT, 10000); -// } - -// return enab; -// } - - BDD SymRS::encEntitySameSuccessor(Process proc_id, Entity entity_id) { return BDD_IFF(encEntity(proc_id, entity_id), encEntitySucc(proc_id, entity_id)); @@ -810,7 +714,7 @@ void SymRS::encodeTransitions(void) prod_conds.resize(numberOfProc); - for (auto proc_id = 0; proc_id < numberOfProc; ++proc_id) { + for (unsigned int proc_id = 0; proc_id < numberOfProc; ++proc_id) { prod_conds[proc_id] = getProductionConditions(proc_id); } diff --git a/reactics-bdd/symrs.hh b/reactics-bdd/symrs.hh index bcb5999..4feef3c 100644 --- a/reactics-bdd/symrs.hh +++ b/reactics-bdd/symrs.hh @@ -15,7 +15,6 @@ #include "types.hh" #include "macro.hh" #include "bdd_macro.hh" -// #include "rs.hh" #include "options.hh" #include "memtime.hh" @@ -240,16 +239,11 @@ class SymRS vector *pv_proc_ctx; BDDvec *pv_proc_ctx_E; - // TODO: remove - BDDvec *pv_act; - BDD *pv_act_E; - unsigned int totalEntities; unsigned int numberOfProc; /*!< The number of DRS processes */ unsigned int totalStateVars; unsigned int totalRctSysStateVars; /*!< Total number of different entities produced by reactions */ unsigned int totalCtxEntities; /*!< Total number of different (process,context) entities used */ - unsigned int totalActions; unsigned int totalCtxAutStateVars; EntitiesForProc usedProducts; /*!< Entities used in products (per process) */ @@ -309,13 +303,6 @@ class SymRS BDD encContext(const EntitiesForProc &proc_entities); - /** - * @brief Complements an encoding of a given state by negating all the variables that are not set to true - * - * @return Returns the encoded state - */ - BDD compState(const BDD &state) const; - BDD compContext(const BDD &context) const; std::string decodedRctSysStateToStr(const BDD &state); diff --git a/reactics-bdd/test.rs b/reactics-bdd/test.drs similarity index 100% rename from reactics-bdd/test.rs rename to reactics-bdd/test.drs diff --git a/reactics-smt/rs/context_automaton.py b/reactics-smt/rs/context_automaton.py index 6d0a084..3b29e68 100644 --- a/reactics-smt/rs/context_automaton.py +++ b/reactics-smt/rs/context_automaton.py @@ -55,10 +55,7 @@ class ContextAutomaton(object): return self._states[self._init_state] def is_state(self, name): - if name in self._states: - return True - else: - return False + return name in self._states def get_state_id(self, name): try: @@ -78,10 +75,7 @@ class ContextAutomaton(object): print(state) def is_valid_rs_set(self, elements): - if set(elements).issubset(self._reaction_system.background_set): - return True - else: - return False + return set(elements).issubset(self._reaction_system.background_set) def is_valid_context(self, context): return self.is_valid_rs_set(context) @@ -95,7 +89,7 @@ class ContextAutomaton(object): return new_set def add_transition(self, src, context_set, dst): - if not type(context_set) is set and not type(context_set) is list: + if not isinstance(context_set, (set, list)): print("Contexts set must be of type set or list") if not self.is_valid_context(context_set): diff --git a/reactics-smt/rs/context_automaton_with_concentrations.py b/reactics-smt/rs/context_automaton_with_concentrations.py index 14b0271..f702df1 100644 --- a/reactics-smt/rs/context_automaton_with_concentrations.py +++ b/reactics-smt/rs/context_automaton_with_concentrations.py @@ -9,12 +9,9 @@ class ContextAutomatonWithConcentrations(ContextAutomaton): super(ContextAutomatonWithConcentrations, self).__init__(reaction_system) def is_valid_context(self, context): - if set([e for e, lvl in context]).issubset( + return set(e for e, lvl in context).issubset( self._reaction_system.background_set - ): - return True - else: - return False + ) def context2str(self, ctx): if len(ctx) == 0: @@ -26,7 +23,7 @@ class ContextAutomatonWithConcentrations(ContextAutomaton): return s def add_transition(self, src, context_set, dst): - if not type(context_set) is set and not type(context_set) is list: + if not isinstance(context_set, (set, list)): print("Contexts set must be of type set or list") if not self.is_valid_context(context_set): diff --git a/reactics-smt/rs/extended_context_automaton.py b/reactics-smt/rs/extended_context_automaton.py index bae9ce2..502cb77 100644 --- a/reactics-smt/rs/extended_context_automaton.py +++ b/reactics-smt/rs/extended_context_automaton.py @@ -70,7 +70,7 @@ class ExtendedContextAutomaton(ContextAutomaton): ctx_reactants, ctx_inhibitors, ctx_products = ctx_reaction - if not type(ctx_products) is set and not type(ctx_products) is list: + if not isinstance(ctx_products, (set, list)): print("Contexts set (context products) must be of type set or list") if not self.is_valid_rs_set(ctx_reactants): diff --git a/reactics-smt/rs/reaction_system.py b/reactics-smt/rs/reaction_system.py index ad10a9f..e1d55fc 100644 --- a/reactics-smt/rs/reaction_system.py +++ b/reactics-smt/rs/reaction_system.py @@ -43,11 +43,8 @@ class ReactionSystem(object): self.add_bg_set_entity(e) def is_in_background_set(self, entity): - """Checks if the given name is valid wrt the background set=""" - if entity in self.background_set: - return True - else: - return False + """Checks if the given name is valid wrt the background set""" + return entity in self.background_set def get_entity_id(self, name): try: diff --git a/reactics-smt/rs/reaction_system_with_concentrations.py b/reactics-smt/rs/reaction_system_with_concentrations.py index 9bdeb28..00967c8 100644 --- a/reactics-smt/rs/reaction_system_with_concentrations.py +++ b/reactics-smt/rs/reaction_system_with_concentrations.py @@ -18,9 +18,9 @@ class ReactionSystemWithConcentrations(ReactionSystem): def add_bg_set_entity(self, e): name = "" def_max_conc = -1 - if type(e) is tuple and len(e) == 2: + if isinstance(e, tuple) and len(e) == 2: name, def_max_conc = e - elif type(e) is str: + elif isinstance(e, str): name = e print("\nWARNING: no maximal concentration level specified for:", e, "\n") else: @@ -46,18 +46,10 @@ class ReactionSystemWithConcentrations(ReactionSystem): def is_valid_entity_with_concentration(self, e): """Sanity check for entities with concentration""" - if type(e) is tuple: - if len(e) == 2 and type(e[1]) is int: - return True + if isinstance(e, (tuple, list)) and len(e) == 2 and isinstance(e[1], int): + return True - if type(e) is list: - if len(e) == 2 and type(e[1]) is int: - return True - - print("FATAL. Invalid entity+concentration: {:s}".format(e)) - exit(1) - - return False + raise RuntimeError("Invalid entity+concentration: " + repr(e)) def get_state_ids(self, state): """Returns entities of the given state without levels""" @@ -415,33 +407,4 @@ class ReactionSystemWithConcentrations(ReactionSystem): return rs -class ReactionSystemWithAutomaton(object): - def __init__(self, reaction_system, context_automaton): - self.rs = reaction_system - self.ca = context_automaton - - def show(self, soft=False): - self.rs.show(soft) - self.ca.show() - - def is_with_concentrations(self): - if not isinstance(self.rs, ReactionSystemWithConcentrations): - return False - if not isinstance(self.ca, ContextAutomatonWithConcentrations): - return False - return True - - def sanity_check(self): - pass - - def get_ordinary_reaction_system_with_automaton(self): - if not self.is_with_concentrations(): - raise RuntimeError("Not RS/CA with concentrations") - - ors = self.rs.get_reaction_system() - oca = self.ca.get_automaton_with_flat_contexts(ors) - - return ReactionSystemWithAutomaton(ors, oca) - - # EOF diff --git a/reactics-smt/rs/reaction_system_with_concentrations_param.py b/reactics-smt/rs/reaction_system_with_concentrations_param.py index ab628dc..1e52492 100644 --- a/reactics-smt/rs/reaction_system_with_concentrations_param.py +++ b/reactics-smt/rs/reaction_system_with_concentrations_param.py @@ -34,9 +34,9 @@ class ReactionSystemWithConcentrationsParam(ReactionSystem): def add_bg_set_entity(self, e): name = "" def_max_conc = -1 - if type(e) is tuple and len(e) == 2: + if isinstance(e, tuple) and len(e) == 2: name, def_max_conc = e - elif type(e) is str: + elif isinstance(e, str): name = e print("\nWARNING: no maximal concentration level specified for:", e, "\n") else: @@ -80,18 +80,10 @@ class ReactionSystemWithConcentrationsParam(ReactionSystem): def is_valid_entity_with_concentration(self, e): """Sanity check for entities with concentration""" - if type(e) is tuple: - if len(e) == 2 and type(e[1]) is int: - return True + if isinstance(e, (tuple, list)) and len(e) == 2 and isinstance(e[1], int): + return True - if type(e) is list: - if len(e) == 2 and type(e[1]) is int: - return True - - print("FATAL. Invalid entity+concentration: {:s}".format(e)) - exit(1) - - return False + raise RuntimeError("Invalid entity+concentration: " + repr(e)) def is_valid_concentration_for_entity(self, entity, level): """Checks the concentration level for entity""" diff --git a/reactics-smt/rs_testing.py b/reactics-smt/rs_testing.py index 727dcce..c492227 100644 --- a/reactics-smt/rs_testing.py +++ b/reactics-smt/rs_testing.py @@ -1,12 +1,10 @@ from rs import * from smt import * -import rs_examples from logics import * from rsltl_shortcuts import * from itertools import chain, combinations -import sys import resource def powerset(iterable,N=None): diff --git a/reactics-smt/rssmt.py b/reactics-smt/rssmt.py index 4ebf902..95398ec 100755 --- a/reactics-smt/rssmt.py +++ b/reactics-smt/rssmt.py @@ -11,7 +11,6 @@ import argparse from rs import * from smt import * import sys -import rs_examples import rs_testing from colour import * diff --git a/reactics-smt/smt/__init__.py b/reactics-smt/smt/__init__.py index dbce176..af9a852 100644 --- a/reactics-smt/smt/__init__.py +++ b/reactics-smt/smt/__init__.py @@ -1,4 +1,3 @@ -# from smt.smt_checker import SmtChecker from smt.smt_checker_rs import SmtCheckerRS from smt.smt_checker_rsc import SmtCheckerRSC from smt.smt_checker_rsc_param import SmtCheckerRSCParam diff --git a/reactics-smt/smt/smt_checker_rs.py b/reactics-smt/smt/smt_checker_rs.py index 88242a4..57ee035 100644 --- a/reactics-smt/smt/smt_checker_rs.py +++ b/reactics-smt/smt/smt_checker_rs.py @@ -267,7 +267,7 @@ class SmtCheckerRS(object): ): """Main testing function""" - if not type(state) is tuple: + if not isinstance(state, tuple): state = (state, []) if print_time: diff --git a/reactics-smt/smt/smt_checker_rsc.py b/reactics-smt/smt/smt_checker_rsc.py index 9b4a795..7934e73 100644 --- a/reactics-smt/smt/smt_checker_rsc.py +++ b/reactics-smt/smt/smt_checker_rsc.py @@ -11,9 +11,6 @@ from colour import * from logics import rsLTL_Encoder -# def simplify(x): -# return x - class SmtCheckerRSC(object): def __init__(self, rsca): diff --git a/reactics-smt/smt/smt_checker_rsc_param.py b/reactics-smt/smt/smt_checker_rsc_param.py index 2d48fcf..c345025 100644 --- a/reactics-smt/smt/smt_checker_rsc_param.py +++ b/reactics-smt/smt/smt_checker_rsc_param.py @@ -14,9 +14,6 @@ from logics import ParamConstr_Encoder from rs.reaction_system_with_concentrations_param import ParameterObj, is_param -# def simplify(x): -# return x - def z3_max(a, b): return If(a > b, a, b) diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 0000000..bc65fc7 --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +# Run all ReactICS tests (BDD, SMT, generators) +exec bash "$(dirname "$0")/tests/run_all_tests.sh" "$@" diff --git a/tests/expected/tgc4_mc.txt b/tests/expected/tgc4_mc.txt new file mode 100644 index 0000000..a1ae75c --- /dev/null +++ b/tests/expected/tgc4_mc.txt @@ -0,0 +1,8 @@ +Using BDD-based Bounded Model Checking +Formula (((EF(E< proc0.allowed >X(proc0.in)) AND EF(E< proc1.allowed >X(proc1.in))) AND EF(E< proc2.allowed >X(proc2.in))) AND EF(E< proc3.allowed >X(proc3.in))) holds +Using BDD-based Bounded Model Checking +Formula EF((((proc0.approach AND proc1.approach) AND proc2.approach) AND proc3.approach)) holds +Using BDD-based Bounded Model Checking +Formula AG((proc0.in IMPLIES K[proc0](((~proc1.in AND ~proc2.in) AND ~proc3.in)))) holds +Using BDD-based Bounded Model Checking +Formula AG((proc0.in IMPLIES C[proc0 proc1 proc2 proc3](((~proc1.in AND ~proc2.in) AND ~proc3.in)))) holds diff --git a/tests/expected/tgc4_print.txt b/tests/expected/tgc4_print.txt new file mode 100644 index 0000000..ccbcebb --- /dev/null +++ b/tests/expected/tgc4_print.txt @@ -0,0 +1,58 @@ +# Context entities: out allowed +# Reactions: + + . proc = "proc0": + * (R={ out },I={ },P={ approach }) + * (R={ approach },I={ req },P={ req }) + * (R={ allowed },I={ },P={ in }) + * (R={ in },I={ },P={ out leave }) + * (R={ req },I={ in },P={ req }) + + . proc = "proc1": + * (R={ out },I={ },P={ approach }) + * (R={ approach },I={ req },P={ req }) + * (R={ allowed },I={ },P={ in }) + * (R={ in },I={ },P={ out leave }) + * (R={ req },I={ in },P={ req }) + + . proc = "proc2": + * (R={ out },I={ },P={ approach }) + * (R={ approach },I={ req },P={ req }) + * (R={ allowed },I={ },P={ in }) + * (R={ in },I={ },P={ out leave }) + * (R={ req },I={ in },P={ req }) + + . proc = "proc3": + * (R={ out },I={ },P={ approach }) + * (R={ approach },I={ req },P={ req }) + * (R={ allowed },I={ },P={ in }) + * (R={ in },I={ },P={ out leave }) + * (R={ req },I={ in },P={ req }) + +# Context Automaton States: + = Init state: init + * init + * green + * red + * T +# Context Automaton Transitions: + * [init -> green]: { proc0={ out } proc1={ out } proc2={ out } proc3={ out } } + * [green -> red]: { proc0={ allowed } } proc0.req + * [green -> red]: { proc1={ allowed } } proc1.req + * [green -> red]: { proc2={ allowed } } proc2.req + * [green -> red]: { proc3={ allowed } } proc3.req + * [green -> green]: { proc0={ } } (((~proc0.req AND ~proc1.req) AND ~proc2.req) AND ~proc3.req) + * [green -> green]: { proc1={ } } (((~proc0.req AND ~proc1.req) AND ~proc2.req) AND ~proc3.req) + * [green -> green]: { proc2={ } } (((~proc0.req AND ~proc1.req) AND ~proc2.req) AND ~proc3.req) + * [green -> green]: { proc3={ } } (((~proc0.req AND ~proc1.req) AND ~proc2.req) AND ~proc3.req) + * [red -> green]: { proc0={ } } proc0.leave + * [red -> green]: { proc1={ } } proc1.leave + * [red -> green]: { proc2={ } } proc2.leave + * [red -> green]: { proc3={ } } proc3.leave + * [red -> red]: { proc0={ } } (((~proc0.leave AND ~proc1.leave) AND ~proc2.leave) AND ~proc3.leave) + * [red -> red]: { proc1={ } } (((~proc0.leave AND ~proc1.leave) AND ~proc2.leave) AND ~proc3.leave) + * [red -> red]: { proc2={ } } (((~proc0.leave AND ~proc1.leave) AND ~proc2.leave) AND ~proc3.leave) + * [red -> red]: { proc3={ } } (((~proc0.leave AND ~proc1.leave) AND ~proc2.leave) AND ~proc3.leave) + * [green -> T]: { } ~((((((((true OR proc0.req) OR proc1.req) OR proc2.req) OR proc3.req) OR (((~proc0.req AND ~proc1.req) AND ~proc2.req) AND ~proc3.req)) OR (((~proc0.req AND ~proc1.req) AND ~proc2.req) AND ~proc3.req)) OR (((~proc0.req AND ~proc1.req) AND ~proc2.req) AND ~proc3.req)) OR (((~proc0.req AND ~proc1.req) AND ~proc2.req) AND ~proc3.req)) + * [red -> T]: { } ~((((((((true OR proc0.leave) OR proc1.leave) OR proc2.leave) OR proc3.leave) OR (((~proc0.leave AND ~proc1.leave) AND ~proc2.leave) AND ~proc3.leave)) OR (((~proc0.leave AND ~proc1.leave) AND ~proc2.leave) AND ~proc3.leave)) OR (((~proc0.leave AND ~proc1.leave) AND ~proc2.leave) AND ~proc3.leave)) OR (((~proc0.leave AND ~proc1.leave) AND ~proc2.leave) AND ~proc3.leave)) + * [T -> T]: { } diff --git a/tests/expected/tgc4_states.txt b/tests/expected/tgc4_states.txt new file mode 100644 index 0000000..bb515cf --- /dev/null +++ b/tests/expected/tgc4_states.txt @@ -0,0 +1,80 @@ +{ proc0={ req } proc1={ approach } proc2={ req } proc3={ out leave } } +{ proc0={ req } proc1={ req } proc2={ approach } proc3={ out leave } } +{ proc0={ req } proc1={ req } proc2={ req } proc3={ out leave } } +{ proc0={ approach } proc1={ approach } proc2={ req } proc3={ out leave } } +{ proc0={ approach } proc1={ req } proc2={ req } proc3={ out leave } } +{ proc0={ approach } proc1={ approach } proc2={ req in } proc3={ approach } } +{ proc0={ approach } proc1={ req } proc2={ out leave } proc3={ req } } +{ proc0={ approach } proc1={ req } proc2={ req in } proc3={ approach } } +{ proc0={ req } proc1={ approach } proc2={ approach } proc3={ out leave } } +{ proc0={ approach } proc1={ approach } proc2={ approach } proc3={ out leave } } +{ proc0={ req } proc1={ approach } proc2={ req in } proc3={ req } } +{ proc0={ approach } proc1={ req } proc2={ approach } proc3={ out leave } } +{ proc0={ req } proc1={ approach } proc2={ req } proc3={ req in } } +{ proc0={ approach } proc1={ req } proc2={ req in } proc3={ req } } +{ proc0={ req } proc1={ approach } proc2={ out leave } proc3={ approach } } +{ proc0={ req in } proc1={ approach } proc2={ req } proc3={ req } } +{ proc0={ approach } proc1={ approach } proc2={ approach } proc3={ req in } } +{ proc0={ req } proc1={ req } proc2={ out leave } proc3={ req } } +{ proc0={ req } proc1={ approach } proc2={ out leave } proc3={ req } } +{ proc0={ req } proc1={ req } proc2={ req in } proc3={ approach } } +{ proc0={ approach } proc1={ approach } proc2={ out leave } proc3={ req } } +{ proc0={ req } proc1={ req } proc2={ req in } proc3={ req } } +{ proc0={ req } proc1={ approach } proc2={ req in } proc3={ approach } } +{ proc0={ approach } proc1={ req } proc2={ out leave } proc3={ approach } } +{ proc0={ req } proc1={ req } proc2={ out leave } proc3={ approach } } +{ proc0={ } proc1={ } proc2={ } proc3={ } } +{ proc0={ approach } proc1={ approach } proc2={ out leave } proc3={ approach } } +{ proc0={ approach } proc1={ approach } proc2={ req in } proc3={ req } } +{ proc0={ req } proc1={ approach } proc2={ approach } proc3={ req in } } +{ proc0={ req in } proc1={ req } proc2={ approach } proc3={ req } } +{ proc0={ req in } proc1={ approach } proc2={ approach } proc3={ approach } } +{ proc0={ req } proc1={ req in } proc2={ req } proc3={ req } } +{ proc0={ req in } proc1={ req } proc2={ approach } proc3={ approach } } +{ proc0={ req } proc1={ req } proc2={ req } proc3={ req in } } +{ proc0={ approach } proc1={ req } proc2={ req } proc3={ req in } } +{ proc0={ approach } proc1={ approach } proc2={ req } proc3={ req in } } +{ proc0={ out leave } proc1={ req } proc2={ approach } proc3={ approach } } +{ proc0={ req in } proc1={ req } proc2={ req } proc3={ req } } +{ proc0={ out leave } proc1={ req } proc2={ req } proc3={ approach } } +{ proc0={ req } proc1={ req } proc2={ approach } proc3={ req in } } +{ proc0={ req in } proc1={ approach } proc2={ approach } proc3={ req } } +{ proc0={ req } proc1={ approach } proc2={ approach } proc3={ req } } +{ proc0={ req in } proc1={ approach } proc2={ req } proc3={ approach } } +{ proc0={ out leave } proc1={ approach } proc2={ req } proc3={ req } } +{ proc0={ approach } proc1={ req } proc2={ approach } proc3={ req in } } +{ proc0={ out leave } proc1={ req } proc2={ approach } proc3={ req } } +{ proc0={ req } proc1={ out leave } proc2={ req } proc3={ req } } +{ proc0={ req } proc1={ approach } proc2={ req } proc3={ req } } +{ proc0={ out leave } proc1={ req } proc2={ req } proc3={ req } } +{ proc0={ approach } proc1={ req in } proc2={ approach } proc3={ approach } } +{ proc0={ out leave } proc1={ approach } proc2={ approach } proc3={ approach } } +{ proc0={ approach } proc1={ approach } proc2={ approach } proc3={ approach } } +{ proc0={ approach } proc1={ req } proc2={ req } proc3={ req } } +{ proc0={ approach } proc1={ out leave } proc2={ approach } proc3={ approach } } +{ proc0={ approach } proc1={ req } proc2={ approach } proc3={ approach } } +{ proc0={ approach } proc1={ out leave } proc2={ req } proc3={ req } } +{ proc0={ req } proc1={ out leave } proc2={ approach } proc3={ req } } +{ proc0={ approach } proc1={ out leave } proc2={ approach } proc3={ req } } +{ proc0={ approach } proc1={ req in } proc2={ req } proc3={ req } } +{ proc0={ out leave } proc1={ approach } proc2={ req } proc3={ approach } } +{ proc0={ approach } proc1={ approach } proc2={ req } proc3={ req } } +{ proc0={ out leave } proc1={ approach } proc2={ approach } proc3={ req } } +{ proc0={ req in } proc1={ req } proc2={ req } proc3={ approach } } +{ proc0={ approach } proc1={ approach } proc2={ approach } proc3={ req } } +{ proc0={ req } proc1={ req } proc2={ approach } proc3={ req } } +{ proc0={ approach } proc1={ req } proc2={ req } proc3={ approach } } +{ proc0={ req } proc1={ approach } proc2={ approach } proc3={ approach } } +{ proc0={ approach } proc1={ req in } proc2={ approach } proc3={ req } } +{ proc0={ approach } proc1={ approach } proc2={ req } proc3={ approach } } +{ proc0={ approach } proc1={ req in } proc2={ req } proc3={ approach } } +{ proc0={ req } proc1={ approach } proc2={ req } proc3={ approach } } +{ proc0={ req } proc1={ req in } proc2={ approach } proc3={ req } } +{ proc0={ approach } proc1={ req } proc2={ approach } proc3={ req } } +{ proc0={ req } proc1={ req in } proc2={ approach } proc3={ approach } } +{ proc0={ req } proc1={ out leave } proc2={ approach } proc3={ approach } } +{ proc0={ req } proc1={ req } proc2={ approach } proc3={ approach } } +{ proc0={ approach } proc1={ out leave } proc2={ req } proc3={ approach } } +{ proc0={ req } proc1={ out leave } proc2={ req } proc3={ approach } } +{ proc0={ req } proc1={ req in } proc2={ req } proc3={ approach } } +{ proc0={ req } proc1={ req } proc2={ req } proc3={ approach } } diff --git a/tests/expected/tgc_mc.txt b/tests/expected/tgc_mc.txt new file mode 100644 index 0000000..6c19266 --- /dev/null +++ b/tests/expected/tgc_mc.txt @@ -0,0 +1,8 @@ +Using BDD-based Bounded Model Checking +Formula ((EF(E< proc0.allowed >X(proc0.in)) AND EF(E< proc1.allowed >X(proc1.in))) AND EF(E< proc2.allowed >X(proc2.in))) holds +Using BDD-based Bounded Model Checking +Formula EF(((proc0.approach AND proc1.approach) AND proc2.approach)) holds +Using BDD-based Bounded Model Checking +Formula AG((proc0.in IMPLIES K[proc0]((~proc1.in AND ~proc2.in)))) holds +Using BDD-based Bounded Model Checking +Formula AG((proc0.in IMPLIES C[proc0 proc1 proc2]((~proc1.in AND ~proc2.in)))) holds diff --git a/tests/expected/tgc_print.txt b/tests/expected/tgc_print.txt new file mode 100644 index 0000000..0685301 --- /dev/null +++ b/tests/expected/tgc_print.txt @@ -0,0 +1,47 @@ +# Context entities: out allowed +# Reactions: + + . proc = "proc0": + * (R={ out },I={ },P={ approach }) + * (R={ approach },I={ req },P={ req }) + * (R={ allowed },I={ },P={ in }) + * (R={ in },I={ },P={ out leave }) + * (R={ req },I={ in },P={ req }) + + . proc = "proc1": + * (R={ out },I={ },P={ approach }) + * (R={ approach },I={ req },P={ req }) + * (R={ allowed },I={ },P={ in }) + * (R={ in },I={ },P={ out leave }) + * (R={ req },I={ in },P={ req }) + + . proc = "proc2": + * (R={ out },I={ },P={ approach }) + * (R={ approach },I={ req },P={ req }) + * (R={ allowed },I={ },P={ in }) + * (R={ in },I={ },P={ out leave }) + * (R={ req },I={ in },P={ req }) + +# Context Automaton States: + = Init state: init + * init + * green + * red + * T +# Context Automaton Transitions: + * [init -> green]: { proc0={ out } proc1={ out } proc2={ out } } + * [green -> red]: { proc0={ allowed } } proc0.req + * [green -> red]: { proc1={ allowed } } proc1.req + * [green -> red]: { proc2={ allowed } } proc2.req + * [green -> green]: { proc0={ } } ((~proc0.req AND ~proc1.req) AND ~proc2.req) + * [green -> green]: { proc1={ } } ((~proc0.req AND ~proc1.req) AND ~proc2.req) + * [green -> green]: { proc2={ } } ((~proc0.req AND ~proc1.req) AND ~proc2.req) + * [red -> green]: { proc0={ } } proc0.leave + * [red -> green]: { proc1={ } } proc1.leave + * [red -> green]: { proc2={ } } proc2.leave + * [red -> red]: { proc0={ } } ((~proc0.leave AND ~proc1.leave) AND ~proc2.leave) + * [red -> red]: { proc1={ } } ((~proc0.leave AND ~proc1.leave) AND ~proc2.leave) + * [red -> red]: { proc2={ } } ((~proc0.leave AND ~proc1.leave) AND ~proc2.leave) + * [green -> T]: { } ~((((((true OR proc0.req) OR proc1.req) OR proc2.req) OR ((~proc0.req AND ~proc1.req) AND ~proc2.req)) OR ((~proc0.req AND ~proc1.req) AND ~proc2.req)) OR ((~proc0.req AND ~proc1.req) AND ~proc2.req)) + * [red -> T]: { } ~((((((true OR proc0.leave) OR proc1.leave) OR proc2.leave) OR ((~proc0.leave AND ~proc1.leave) AND ~proc2.leave)) OR ((~proc0.leave AND ~proc1.leave) AND ~proc2.leave)) OR ((~proc0.leave AND ~proc1.leave) AND ~proc2.leave)) + * [T -> T]: { } diff --git a/tests/expected/tgc_reactions.txt b/tests/expected/tgc_reactions.txt new file mode 100644 index 0000000..6b44f93 --- /dev/null +++ b/tests/expected/tgc_reactions.txt @@ -0,0 +1,23 @@ +# Reactions: + + . proc = "proc0": + * (R={ out },I={ },P={ approach }) + * (R={ approach },I={ req },P={ req }) + * (R={ allowed },I={ },P={ in }) + * (R={ in },I={ },P={ out leave }) + * (R={ req },I={ in },P={ req }) + + . proc = "proc1": + * (R={ out },I={ },P={ approach }) + * (R={ approach },I={ req },P={ req }) + * (R={ allowed },I={ },P={ in }) + * (R={ in },I={ },P={ out leave }) + * (R={ req },I={ in },P={ req }) + + . proc = "proc2": + * (R={ out },I={ },P={ approach }) + * (R={ approach },I={ req },P={ req }) + * (R={ allowed },I={ },P={ in }) + * (R={ in },I={ },P={ out leave }) + * (R={ req },I={ in },P={ req }) + diff --git a/tests/expected/tgc_states.txt b/tests/expected/tgc_states.txt new file mode 100644 index 0000000..d48f59b --- /dev/null +++ b/tests/expected/tgc_states.txt @@ -0,0 +1,32 @@ +{ proc0={ req in } proc1={ req } proc2={ req } } +{ proc0={ req in } proc1={ req } proc2={ approach } } +{ proc0={ req } proc1={ req in } proc2={ approach } } +{ proc0={ req in } proc1={ approach } proc2={ req } } +{ proc0={ req in } proc1={ approach } proc2={ approach } } +{ proc0={ req } proc1={ req in } proc2={ req } } +{ proc0={ approach } proc1={ req in } proc2={ approach } } +{ proc0={ approach } proc1={ approach } proc2={ req in } } +{ proc0={ approach } proc1={ req in } proc2={ req } } +{ proc0={ approach } proc1={ out leave } proc2={ req } } +{ proc0={ req } proc1={ approach } proc2={ out leave } } +{ proc0={ req } proc1={ req } proc2={ req in } } +{ proc0={ approach } proc1={ req } proc2={ req } } +{ proc0={ approach } proc1={ req } proc2={ req in } } +{ proc0={ out leave } proc1={ req } proc2={ req } } +{ proc0={ req } proc1={ approach } proc2={ req in } } +{ proc0={ req } proc1={ out leave } proc2={ approach } } +{ proc0={ approach } proc1={ out leave } proc2={ approach } } +{ proc0={ req } proc1={ req } proc2={ out leave } } +{ proc0={ req } proc1={ out leave } proc2={ req } } +{ proc0={ } proc1={ } proc2={ } } +{ proc0={ req } proc1={ approach } proc2={ req } } +{ proc0={ out leave } proc1={ req } proc2={ approach } } +{ proc0={ approach } proc1={ req } proc2={ out leave } } +{ proc0={ approach } proc1={ approach } proc2={ req } } +{ proc0={ req } proc1={ req } proc2={ approach } } +{ proc0={ out leave } proc1={ approach } proc2={ req } } +{ proc0={ approach } proc1={ req } proc2={ approach } } +{ proc0={ approach } proc1={ approach } proc2={ approach } } +{ proc0={ approach } proc1={ approach } proc2={ out leave } } +{ proc0={ out leave } proc1={ approach } proc2={ approach } } +{ proc0={ req } proc1={ approach } proc2={ approach } } diff --git a/tests/expected/trivial_print.txt b/tests/expected/trivial_print.txt new file mode 100644 index 0000000..ad83c3e --- /dev/null +++ b/tests/expected/trivial_print.txt @@ -0,0 +1,17 @@ +# Context entities: e1 e4 +# Reactions: + + . proc = "m": + * (R={ e1 e4 },I={ e2 },P={ e1 e2 }) + * (R={ e2 },I={ e4 },P={ e1 e4 e3 }) + * (R={ e1 e3 },I={ e2 },P={ e1 e2 }) + * (R={ e3 },I={ e2 },P={ e1 }) + +# Context Automaton States: + = Init state: s0 + * s0 + * s1 +# Context Automaton Transitions: + * [s0 -> s1]: { m={ e1 e4 } } + * [s1 -> s1]: { m={ } } + * [s1 -> s1]: { m={ e4 } } diff --git a/tests/expected/trivial_reactions.txt b/tests/expected/trivial_reactions.txt new file mode 100644 index 0000000..0149240 --- /dev/null +++ b/tests/expected/trivial_reactions.txt @@ -0,0 +1,8 @@ +# Reactions: + + . proc = "m": + * (R={ e1 e4 },I={ e2 },P={ e1 e2 }) + * (R={ e2 },I={ e4 },P={ e1 e4 e3 }) + * (R={ e1 e3 },I={ e2 },P={ e1 e2 }) + * (R={ e3 },I={ e2 },P={ e1 }) + diff --git a/tests/expected/trivial_states.txt b/tests/expected/trivial_states.txt new file mode 100644 index 0000000..53dc9d6 --- /dev/null +++ b/tests/expected/trivial_states.txt @@ -0,0 +1,3 @@ +{ m={ e1 e4 e3 } } +{ m={ e1 e2 } } +{ m={ } } diff --git a/tests/run_all_tests.sh b/tests/run_all_tests.sh new file mode 100755 index 0000000..bbfa9d3 --- /dev/null +++ b/tests/run_all_tests.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash + +# Runs all ReactICS regression tests (BDD + SMT + generators) + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +REACTICS="$ROOT_DIR/reactics-bdd/reactics" +GENERATORS="$ROOT_DIR/examples/bdd/generators" + +cd "$ROOT_DIR" + +failed=0 + +# --------------------------------------------------------------- +echo "========== BDD Tests ==========" +echo +bash "$SCRIPT_DIR/run_tests.sh" || failed=1 + +# --------------------------------------------------------------- +echo +echo "========== SMT Tests (pytest) ==========" +echo +python3 -m pytest "$SCRIPT_DIR/test_smt.py" -v || failed=1 + +# --------------------------------------------------------------- +echo +echo "========== Generator Tests ==========" +echo + +gen_pass=0 +gen_fail=0 + +check_gen() { + local name="$1" + local gen_cmd="$2" + local property="$3" + local expected="$4" + + local tmpfile + tmpfile=$(mktemp /tmp/reactics_gen_XXXXXX.drs) + + eval "$gen_cmd" > "$tmpfile" 2>&1 + local result + result=$("$REACTICS" -c "$property" "$tmpfile" 2>&1 | grep -oE "(holds|does not hold)" || true) + rm -f "$tmpfile" + + if [[ "$result" == "$expected" ]]; then + echo " PASS: $name" + ((gen_pass++)) + else + echo " FAIL: $name (expected '$expected', got '$result')" + ((gen_fail++)) + failed=1 + fi +} + +echo "[gen_tgc]" +check_gen "tgc(3) f1" "python3 $GENERATORS/gen_tgc.py 3" f1 "holds" +check_gen "tgc(3) f3" "python3 $GENERATORS/gen_tgc.py 3" f3 "holds" +check_gen "tgc(4) f2" "python3 $GENERATORS/gen_tgc.py 4" f2 "holds" + +echo +echo "[gen_asm]" +check_gen "asm(3) f1" "python3 $GENERATORS/gen_asm.py 3" f1 "holds" +check_gen "asm(3) f2" "python3 $GENERATORS/gen_asm.py 3" f2 "holds" + +echo +echo "[gen_drs]" +check_gen "drs(2,2,2,a) f0" "python3 $GENERATORS/gen_drs.py 2 2 2 a" f0 "does not hold" +check_gen "drs(2,2,2,b) f0" "python3 $GENERATORS/gen_drs.py 2 2 2 b" f0 "holds" + +echo +echo "Generator results: $gen_pass passed, $gen_fail failed" + +# --------------------------------------------------------------- +echo +echo "================================" +if [[ $failed -eq 0 ]]; then + echo "All test suites passed." +else + echo "Some tests FAILED." + exit 1 +fi diff --git a/tests/run_tests.sh b/tests/run_tests.sh new file mode 100755 index 0000000..b64a847 --- /dev/null +++ b/tests/run_tests.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash + +# Regression test harness for ReactICS BDD module +# Compares current output against saved expected output + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +REACTICS="$ROOT_DIR/reactics-bdd/reactics" +EXPECTED_DIR="$SCRIPT_DIR/expected" + +PASS=0 +FAIL=0 +ERRORS="" + +run_test() { + local name="$1" + local expected_file="$2" + shift 2 + local actual + actual=$("$@" 2>&1) || true + local expected + expected=$(cat "$expected_file") + + if [[ "$actual" == "$expected" ]]; then + echo " PASS: $name" + ((PASS++)) + else + echo " FAIL: $name" + diff <(echo "$expected") <(echo "$actual") | head -20 + ((FAIL++)) + ERRORS="$ERRORS\n - $name" + fi +} + +echo "Running ReactICS regression tests..." +echo + +# --- tgc.drs tests --- +echo "[tgc]" +run_test "tgc print" "$EXPECTED_DIR/tgc_print.txt" "$REACTICS" -P examples/bdd/tgc.drs +run_test "tgc reactions" "$EXPECTED_DIR/tgc_reactions.txt" "$REACTICS" -r examples/bdd/tgc.drs +run_test "tgc states" "$EXPECTED_DIR/tgc_states.txt" "$REACTICS" -s examples/bdd/tgc.drs +run_test "tgc mc (f1-f4)" "$EXPECTED_DIR/tgc_mc.txt" \ + bash -c 'for f in f1 f2 f3 f4; do '"$REACTICS"' -c $f examples/bdd/tgc.drs 2>&1; done' + +echo +echo "[trivial]" +run_test "trivial print" "$EXPECTED_DIR/trivial_print.txt" "$REACTICS" -P reactics-bdd/in/trivial.drs +run_test "trivial reactions" "$EXPECTED_DIR/trivial_reactions.txt" "$REACTICS" -r reactics-bdd/in/trivial.drs +run_test "trivial states" "$EXPECTED_DIR/trivial_states.txt" "$REACTICS" -s reactics-bdd/in/trivial.drs + +echo +echo "[tgc4]" +run_test "tgc4 print" "$EXPECTED_DIR/tgc4_print.txt" "$REACTICS" -P examples/bdd/tgc4.drs +run_test "tgc4 states" "$EXPECTED_DIR/tgc4_states.txt" "$REACTICS" -s examples/bdd/tgc4.drs +run_test "tgc4 mc (f1-f4)" "$EXPECTED_DIR/tgc4_mc.txt" \ + bash -c 'for f in f1 f2 f3 f4; do '"$REACTICS"' -c $f examples/bdd/tgc4.drs 2>&1; done' + +echo +echo "================================" +echo "Results: $PASS passed, $FAIL failed" +if [[ $FAIL -gt 0 ]]; then + echo -e "Failures:$ERRORS" + exit 1 +else + echo "All tests passed." +fi diff --git a/tests/test_smt.py b/tests/test_smt.py new file mode 100644 index 0000000..17f1b02 --- /dev/null +++ b/tests/test_smt.py @@ -0,0 +1,501 @@ +""" +Regression tests for the ReactICS SMT module. + +Tests the Python API directly: reaction system construction, +context automata, and SMT-based verification (reachability + rsLTL). +""" + +import sys +import os +import io +from contextlib import redirect_stdout + +# Add reactics-smt to the path +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "reactics-smt")) + +from z3 import sat, unsat +from rs import ( + ReactionSystem, + ContextAutomaton, + ReactionSystemWithConcentrations, + ContextAutomatonWithConcentrations, + ReactionSystemWithConcentrationsParam, + ReactionSystemWithAutomaton, +) +from smt import SmtCheckerRS, SmtCheckerRSC, SmtCheckerRSCParam +from logics import Formula_rsLTL, BagDescription +from rsltl_shortcuts import ltl_F, bag_entity, param_entity, param_And + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def run_silent(fn, *args, **kwargs): + """Run a function with stdout suppressed, return its result.""" + with redirect_stdout(io.StringIO()): + return fn(*args, **kwargs) + + +# --------------------------------------------------------------------------- +# Data model tests +# --------------------------------------------------------------------------- + +class TestReactionSystem: + def test_add_entities(self): + rs = ReactionSystem() + rs.add_bg_set_entity("a") + rs.add_bg_set_entity("b") + assert rs.background_set == ["a", "b"] + assert rs.get_entity_id("a") == 0 + assert rs.get_entity_id("b") == 1 + assert rs.get_entity_name(0) == "a" + + def test_add_reaction(self): + rs = ReactionSystem() + rs.add_bg_set_entity("a") + rs.add_bg_set_entity("b") + rs.add_bg_set_entity("c") + rs.add_reaction(["a"], ["b"], ["c"]) + assert len(rs.reactions) == 1 + reactants, inhibitors, products = rs.reactions[0] + assert reactants == [0] + assert inhibitors == [1] + assert products == [2] + + def test_reactions_by_product(self): + rs = ReactionSystem() + rs.add_bg_set_entity("a") + rs.add_bg_set_entity("b") + rs.add_reaction(["a"], [], ["b"]) + rbp = rs.get_reactions_by_product() + assert 1 in rbp # entity id for "b" + assert len(rbp[1]) == 1 + + def test_duplicate_entity_raises(self): + rs = ReactionSystem() + rs.add_bg_set_entity("a") + try: + rs.add_bg_set_entity("a") + assert False, "Should have raised" + except RuntimeError: + pass + + +class TestReactionSystemWithConcentrations: + def test_add_entity_with_concentration(self): + rs = ReactionSystemWithConcentrations() + rs.add_bg_set_entity(("x", 3)) + rs.add_bg_set_entity(("y", 5)) + assert rs.background_set == ["x", "y"] + assert rs.get_max_concentration_level(0) == 3 + assert rs.get_max_concentration_level(1) == 5 + + def test_add_reaction_with_concentrations(self): + rs = ReactionSystemWithConcentrations() + rs.add_bg_set_entity(("a", 2)) + rs.add_bg_set_entity(("b", 1)) + rs.add_reaction([("a", 1)], [], [("b", 1)]) + assert len(rs.reactions) == 1 + + def test_meta_reactions(self): + rs = ReactionSystemWithConcentrations() + rs.add_bg_set_entity(("e", 3)) + rs.add_bg_set_entity(("inc", 1)) + rs.add_reaction_inc("e", "inc", [("e", 1)], [("e", 3)]) + e_id = rs.get_entity_id("e") + assert e_id in rs.meta_reactions + assert rs.meta_reactions[e_id][0][0] == "inc" + + +class TestContextAutomaton: + def test_states_and_transitions(self): + rs = ReactionSystem() + rs.add_bg_set_entity("a") + rs.add_bg_set_entity("b") + ca = ContextAutomaton(rs) + ca.add_init_state("s0") + ca.add_state("s1") + assert ca.get_init_state_id() == 0 + assert ca.get_state_id("s1") == 1 + ca.add_transition("s0", ["a"], "s1") + ca.add_transition("s1", [], "s1") + assert len(ca.transitions) == 2 + + def test_invalid_transition_raises(self): + rs = ReactionSystem() + rs.add_bg_set_entity("a") + ca = ContextAutomaton(rs) + ca.add_init_state("s0") + try: + ca.add_transition("s0", ["a"], "nonexistent") + assert False, "Should have raised" + except RuntimeError: + pass + + +class TestContextAutomatonWithConcentrations: + def test_transitions_with_concentrations(self): + rs = ReactionSystemWithConcentrations() + rs.add_bg_set_entity(("a", 2)) + rs.add_bg_set_entity(("b", 1)) + ca = ContextAutomatonWithConcentrations(rs) + ca.add_init_state("s0") + ca.add_state("s1") + ca.add_transition("s0", [("a", 2)], "s1") + ca.add_transition("s1", [], "s1") + assert len(ca.transitions) == 2 + + +# --------------------------------------------------------------------------- +# Formula construction tests +# --------------------------------------------------------------------------- + +class TestFormulas: + def test_bag_description(self): + b = BagDescription.f_entity("x") + assert str(b) == "x" + b_gt = b > 0 + assert ">" in str(b_gt) + + def test_rsltl_formula(self): + f = Formula_rsLTL.f_F( + BagDescription.f_TRUE(), + BagDescription.f_entity("x") > 0, + ) + assert "F" in str(f) + + def test_combined_formula(self): + f = Formula_rsLTL.f_G( + BagDescription.f_TRUE(), + Formula_rsLTL.f_Implies( + BagDescription.f_entity("a") == 1, + Formula_rsLTL.f_F( + BagDescription.f_TRUE(), + BagDescription.f_entity("b") > 0, + ), + ), + ) + assert "G" in str(f) + assert "F" in str(f) + + +# --------------------------------------------------------------------------- +# Verification tests: SmtCheckerRS (basic, no concentrations) +# --------------------------------------------------------------------------- + +def _build_simple_rs(): + """A minimal RS for testing: a -> b -> c with context automaton.""" + rs = ReactionSystem() + rs.add_bg_set_entity("a") + rs.add_bg_set_entity("b") + rs.add_bg_set_entity("c") + rs.add_reaction(["a"], [], ["b"]) + rs.add_reaction(["b"], [], ["c"]) + + ca = ContextAutomaton(rs) + ca.add_init_state("s0") + ca.add_state("s1") + ca.add_transition("s0", ["a"], "s1") + ca.add_transition("s1", [], "s1") + + return ReactionSystemWithAutomaton(rs, ca) + + +class TestSmtCheckerRS: + def test_reachability_sat(self): + rsa = _build_simple_rs() + checker = SmtCheckerRS(rsa) + run_silent( + checker.check_reachability, + ["c"], + print_witness=False, + print_time=False, + print_mem=False, + ) + # After finding SAT, solver state should be satisfiable + assert checker.solver.check() == sat + + def test_reachability_unreachable(self): + """Entity 'a' is never produced, so it shouldn't be reachable as a final state.""" + rs = ReactionSystem() + rs.add_bg_set_entity("a") + rs.add_bg_set_entity("b") + rs.add_reaction(["a"], [], ["b"]) + + ca = ContextAutomaton(rs) + ca.add_init_state("s0") + ca.add_state("s1") + # Context provides 'a' initially, then nothing + ca.add_transition("s0", ["a"], "s1") + ca.add_transition("s1", [], "s1") + + rsa = ReactionSystemWithAutomaton(rs, ca) + checker = SmtCheckerRS(rsa) + + # 'b' is reachable (produced from 'a') + run_silent( + checker.check_reachability, + ["b"], + print_witness=False, + print_time=False, + print_mem=False, + ) + assert checker.solver.check() == sat + + +# --------------------------------------------------------------------------- +# Verification tests: SmtCheckerRSC (with concentrations) +# --------------------------------------------------------------------------- + +def _build_chain_rsc(chain_len=2, max_conc=2): + """Build a chain reaction system with concentrations.""" + r = ReactionSystemWithConcentrations() + r.add_bg_set_entity(("inc", 1)) + r.add_bg_set_entity(("dec", 1)) + + for i in range(1, chain_len + 1): + r.add_bg_set_entity(("e_" + str(i), max_conc)) + + for i in range(1, chain_len + 1): + ent = "e_" + str(i) + r.add_reaction_inc(ent, "inc", [(ent, 1)], [(ent, max_conc)]) + r.add_reaction_dec(ent, "dec", [(ent, 1)], []) + if i < chain_len: + r.add_reaction([(ent, max_conc)], [], [("e_" + str(i + 1), 1)]) + + r.add_reaction( + [("e_" + str(chain_len), max_conc)], + [("dec", 1)], + [("e_" + str(chain_len), max_conc)], + ) + + c = ContextAutomatonWithConcentrations(r) + c.add_init_state("init") + c.add_state("working") + c.add_transition("init", [("e_1", 1), ("inc", 1)], "working") + c.add_transition("working", [("inc", 1)], "working") + + return ReactionSystemWithAutomaton(r, c) + + +class TestSmtCheckerRSC: + def test_chain_reachability_sat(self): + """Chain(2,2): e_2=2 should be reachable at level 3.""" + rc = _build_chain_rsc(2, 2) + checker = SmtCheckerRSC(rc) + prop = ([("e_2", 2)], []) + run_silent( + checker.check_reachability, + prop, + print_witness=False, + print_time=False, + print_mem=False, + max_level=10, + ) + assert checker.solver.check() == sat + assert checker.current_level == 3 + + def test_chain_reachability_longer(self): + """Chain(3,2): e_3=2 should be reachable at level 5.""" + rc = _build_chain_rsc(3, 2) + checker = SmtCheckerRSC(rc) + prop = ([("e_3", 2)], []) + run_silent( + checker.check_reachability, + prop, + print_witness=False, + print_time=False, + print_mem=False, + max_level=10, + ) + assert checker.solver.check() == sat + assert checker.current_level == 5 + + def test_rsltl_finally(self): + """Check rsLTL F formula on chain(2,2).""" + rc = _build_chain_rsc(2, 2) + checker = SmtCheckerRSC(rc) + form = Formula_rsLTL.f_F( + BagDescription.f_entity("inc") > 0, + BagDescription.f_entity("e_2") >= 2, + ) + run_silent( + checker.check_rsltl, + formula=form, + print_witness=False, + print_time=False, + print_mem=False, + ) + assert checker.solver.check() == sat + assert checker.current_level == 3 + + def test_rsltl_finally_fast(self): + """Chain(2,2), F(e_1==2): should be SAT at level 1.""" + rc = _build_chain_rsc(2, 2) + checker = SmtCheckerRSC(rc) + form = Formula_rsLTL.f_F( + BagDescription.f_entity("inc") > 0, + BagDescription.f_entity("e_1") == 2, + ) + run_silent( + checker.check_rsltl, + formula=form, + print_witness=False, + print_time=False, + print_mem=False, + ) + assert checker.solver.check() == sat + assert checker.current_level == 1 + + def test_rsltl_next_until(self): + """Chain(2,2), X[TRUE](e_1 > 0 U[inc > 0] e_2 > 0): SAT at level 2.""" + rc = _build_chain_rsc(2, 2) + checker = SmtCheckerRSC(rc) + form = Formula_rsLTL.f_X( + BagDescription.f_TRUE(), + Formula_rsLTL.f_U( + BagDescription.f_entity("inc") > 0, + BagDescription.f_entity("e_1") > 0, + BagDescription.f_entity("e_2") > 0, + ), + ) + run_silent( + checker.check_rsltl, + formula=form, + print_witness=False, + print_time=False, + print_mem=False, + ) + assert checker.solver.check() == sat + assert checker.current_level == 2 + + +# --------------------------------------------------------------------------- +# Verification tests: SmtCheckerRSC on heat shock response +# --------------------------------------------------------------------------- + +def _build_heat_shock(): + """Heat shock response model.""" + stress_temp = 42 + max_temp = 50 + + r = ReactionSystemWithConcentrations() + for e in [("hsp", 1), ("hsf", 1), ("hsf2", 1), ("hsf3", 1), ("hse", 1), + ("mfp", 1), ("prot", 1), ("hsf3:hse", 1), ("hsp:mfp", 1), + ("hsp:hsf", 1), ("temp", max_temp), ("heat", 1), ("cool", 1)]: + r.add_bg_set_entity(e) + + r.add_reaction([("hsf", 1)], [("hsp", 1)], [("hsf3", 1)]) + r.add_reaction([("hsf", 1), ("hsp", 1), ("mfp", 1)], [], [("hsf3", 1)]) + r.add_reaction([("hsf3", 1)], [("hse", 1), ("hsp", 1)], [("hsf", 1)]) + r.add_reaction([("hsf3", 1), ("hsp", 1), ("mfp", 1)], [("hse", 1)], [("hsf", 1)]) + r.add_reaction([("hsf3", 1), ("hse", 1)], [("hsp", 1)], [("hsf3:hse", 1)]) + r.add_reaction([("hsp", 1), ("hsf3", 1), ("mfp", 1), ("hse", 1)], [], [("hsf3:hse", 1)]) + r.add_reaction([("hse", 1)], [("hsf3", 1)], [("hse", 1)]) + r.add_reaction([("hsp", 1), ("hsf3", 1), ("hse", 1)], [("mfp", 1)], [("hse", 1)]) + r.add_reaction([("hsf3:hse", 1)], [("hsp", 1)], [("hsp", 1), ("hsf3:hse", 1)]) + r.add_reaction([("hsp", 1), ("mfp", 1), ("hsf3:hse", 1)], [], [("hsp", 1), ("hsf3:hse", 1)]) + r.add_reaction([("hsf", 1), ("hsp", 1)], [("mfp", 1)], [("hsp:hsf", 1)]) + r.add_reaction([("hsp:hsf", 1), ("temp", stress_temp)], [], [("hsf", 1), ("hsp", 1)]) + r.add_reaction([("hsp:hsf", 1)], [("temp", stress_temp)], [("hsp:hsf", 1)]) + r.add_reaction([("hsp", 1), ("hsf3:hse", 1)], [("mfp", 1)], [("hse", 1), ("hsp:hsf", 1)]) + r.add_reaction([("temp", stress_temp), ("prot", 1)], [], [("mfp", 1), ("prot", 1)]) + r.add_reaction([("prot", 1)], [("temp", stress_temp)], [("prot", 1)]) + r.add_reaction([("hsp", 1), ("mfp", 1)], [], [("hsp:mfp", 1)]) + r.add_reaction([("mfp", 1)], [("hsp", 1)], [("mfp", 1)]) + r.add_reaction([("hsp:mfp", 1)], [], [("hsp", 1), ("prot", 1)]) + + r.add_reaction_inc("temp", "heat", [("temp", 1)], [("temp", max_temp)]) + r.add_reaction_dec("temp", "cool", [("temp", 1)], []) + r.add_permanency("temp", [("heat", 1), ("cool", 1)]) + + c = ContextAutomatonWithConcentrations(r) + c.add_init_state("0") + c.add_state("1") + c.add_transition("0", [("hsf", 1), ("prot", 1), ("hse", 1), ("temp", 35)], "1") + c.add_transition("1", [("cool", 1)], "1") + c.add_transition("1", [("heat", 1)], "1") + c.add_transition("1", [], "1") + + return ReactionSystemWithAutomaton(r, c) + + +class TestHeatShockResponse: + def test_mfp_reachable(self): + """mfp should be reachable at level 9.""" + rc = _build_heat_shock() + checker = SmtCheckerRSC(rc) + prop = ([("mfp", 1)], []) + run_silent( + checker.check_reachability, + prop, + print_witness=False, + print_time=False, + print_mem=False, + max_level=40, + ) + assert checker.solver.check() == sat + assert checker.current_level == 9 + + +# --------------------------------------------------------------------------- +# Verification tests: SmtCheckerRSCParam (parametric) +# --------------------------------------------------------------------------- + +def _build_parametric_overview(): + """Parametric example from doc/overview.py.""" + prs = ReactionSystemWithConcentrationsParam() + for ec in [("a", 3), ("b", 2), ("c", 1), ("h", 1)]: + prs.add_bg_set_entity(ec) + + lda = prs.get_param("lda") + prs.add_reaction([("a", 1)], [("h", 1)], [("b", 2)]) + prs.add_reaction(lda, [("h", 1)], [("c", 1)]) + + ca = ContextAutomatonWithConcentrations(prs) + ca.add_init_state("0") + ca.add_state("1") + ca.add_transition("0", [("a", 3)], "1") + ca.add_transition("1", [], "1") + ca.add_transition("1", [("h", 1)], "1") + + return ReactionSystemWithAutomaton(prs, ca), lda + + +class TestSmtCheckerRSCParam: + def test_parametric_sat(self): + """Parametric overview: F[h==0](c>0) should be SAT at level 2.""" + rc, lda = _build_parametric_overview() + pc = param_entity(lda, "a") == 0 + f = ltl_F(bag_entity("h") == 0, "c") + + checker = SmtCheckerRSCParam(rc, optimise=True) + run_silent( + checker.check_rsltl, + formulae_list=[f], + param_constr=pc, + ) + # The parametric checker uses path-based solving; + # after SAT the solver should be satisfiable + assert checker.solver.check() == sat + + +# --------------------------------------------------------------------------- +# RS translation tests (RSC -> RS) +# --------------------------------------------------------------------------- + +class TestRSCtoRSTranslation: + def test_translated_system_reachability(self): + """Chain(2,2) translated to plain RS: e_2#2 should be reachable.""" + rc = _build_chain_rsc(2, 2) + orc = rc.get_ordinary_reaction_system_with_automaton() + checker = SmtCheckerRS(orc) + run_silent( + checker.check_reachability, + ["e_2#2"], + print_witness=False, + print_time=False, + print_mem=False, + ) + assert checker.solver.check() == sat -- 2.53.0 From ae419e5e0e0f4e3abe200e8d8c452ebe13996b21 Mon Sep 17 00:00:00 2001 From: Artur Meski Date: Fri, 10 Apr 2026 18:27:57 +0100 Subject: [PATCH 12/12] Failure handling (CI) --- .github/workflows/tests.yml | 19 ++++++++++++++++--- tests/run_all_tests.sh | 13 ++++++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6f61a61..279edf6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -72,21 +72,30 @@ jobs: python-version: "3.12" - name: Run generator tests + shell: bash --noprofile --norc {0} run: | + set -uo pipefail REACTICS="$PWD/reactics-bdd/reactics" GENERATORS="$PWD/examples/bdd/generators" + passed=0 failed=0 check_gen() { local name="$1" gen_cmd="$2" property="$3" expected="$4" local tmpfile=$(mktemp /tmp/reactics_gen_XXXXXX.drs) - eval "$gen_cmd" > "$tmpfile" 2>&1 + if ! eval "$gen_cmd" > "$tmpfile" 2>&1; then + echo " FAIL: $name (generator crashed)" + failed=$((failed + 1)) + rm -f "$tmpfile" + return + fi local result=$("$REACTICS" -c "$property" "$tmpfile" 2>&1 | grep -oE "(holds|does not hold)" || true) rm -f "$tmpfile" if [ "$result" = "$expected" ]; then echo " PASS: $name" + passed=$((passed + 1)) else echo " FAIL: $name (expected '$expected', got '$result')" - failed=1 + failed=$((failed + 1)) fi } check_gen "tgc(3) f1" "python3 $GENERATORS/gen_tgc.py 3" f1 "holds" @@ -96,4 +105,8 @@ jobs: check_gen "asm(3) f2" "python3 $GENERATORS/gen_asm.py 3" f2 "holds" check_gen "drs(2,2,2,a) f0" "python3 $GENERATORS/gen_drs.py 2 2 2 a" f0 "does not hold" check_gen "drs(2,2,2,b) f0" "python3 $GENERATORS/gen_drs.py 2 2 2 b" f0 "holds" - exit $failed + echo + echo "Generator results: $passed passed, $failed failed" + if [ "$failed" -gt 0 ]; then + exit 1 + fi diff --git a/tests/run_all_tests.sh b/tests/run_all_tests.sh index bbfa9d3..c326d9d 100755 --- a/tests/run_all_tests.sh +++ b/tests/run_all_tests.sh @@ -41,17 +41,24 @@ check_gen() { local tmpfile tmpfile=$(mktemp /tmp/reactics_gen_XXXXXX.drs) - eval "$gen_cmd" > "$tmpfile" 2>&1 + if ! eval "$gen_cmd" > "$tmpfile" 2>&1; then + echo " FAIL: $name (generator crashed)" + gen_fail=$((gen_fail + 1)) + failed=1 + rm -f "$tmpfile" + return + fi + local result result=$("$REACTICS" -c "$property" "$tmpfile" 2>&1 | grep -oE "(holds|does not hold)" || true) rm -f "$tmpfile" if [[ "$result" == "$expected" ]]; then echo " PASS: $name" - ((gen_pass++)) + gen_pass=$((gen_pass + 1)) else echo " FAIL: $name (expected '$expected', got '$result')" - ((gen_fail++)) + gen_fail=$((gen_fail + 1)) failed=1 fi } -- 2.53.0